mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-09 22:38:10 -05:00
Compare commits
270 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70f8c013f3 | ||
|
|
8f6e2a3d4a | ||
|
|
fad176a0a8 | ||
|
|
dd213eb965 | ||
|
|
d205dbcdac | ||
|
|
f8ff9129b5 | ||
|
|
f9d01b5ebb | ||
|
|
2c7f4753a2 | ||
|
|
9b261b9adf | ||
|
|
a23b6d518f | ||
|
|
bc73bdb704 | ||
|
|
f22c144786 | ||
|
|
eb759251ad | ||
|
|
19b512c3ab | ||
|
|
a4ce90970a | ||
|
|
8d2fda3af9 | ||
|
|
aa59d58deb | ||
|
|
d209ee38c7 | ||
|
|
c20be027fe | ||
|
|
3ef3509bfd | ||
|
|
7142b020ef | ||
|
|
1b9f07b525 | ||
|
|
dcfc94ca07 | ||
|
|
0e85861a46 | ||
|
|
7c5a040287 | ||
|
|
08eb48c2e7 | ||
|
|
e40d4e6623 | ||
|
|
51bd1ebadf | ||
|
|
d3de731967 | ||
|
|
458b0a5e1c | ||
|
|
b8f64bd554 | ||
|
|
1622a34331 | ||
|
|
6b9f4c1fb8 | ||
|
|
4d2061a641 | ||
|
|
713f6e46fe | ||
|
|
efadc81974 | ||
|
|
ea54f60dcc | ||
|
|
4008125e37 | ||
|
|
da94411bf3 | ||
|
|
ab7b37be10 | ||
|
|
772337bf0d | ||
|
|
1e30c4e136 | ||
|
|
e12a40ad4f | ||
|
|
97beaecbeb | ||
|
|
7af6817bac | ||
|
|
50ecc32d85 | ||
|
|
ff1ef380a7 | ||
|
|
6a3a7e82d1 | ||
|
|
34bc0b5e31 | ||
|
|
ce59999503 | ||
|
|
9bb4ccf740 | ||
|
|
900b13f08c | ||
|
|
6824f0c0a7 | ||
|
|
a2481406db | ||
|
|
171f7eb3ab | ||
|
|
dccc70c433 | ||
|
|
e5ec9acfac | ||
|
|
f0eb9f90a3 | ||
|
|
758425f98a | ||
|
|
b4b5b0a4d9 | ||
|
|
81a47ecab7 | ||
|
|
0bce5c7b6e | ||
|
|
992936dbd8 | ||
|
|
48d74290f3 | ||
|
|
3d4e967b92 | ||
|
|
d8690c7cec | ||
|
|
7eed9c3c64 | ||
|
|
97b75cb153 | ||
|
|
b485a4584f | ||
|
|
f4dbafc638 | ||
|
|
eae56e0038 | ||
|
|
72a5e49855 | ||
|
|
17b7d96da1 | ||
|
|
1b2d9ec0ed | ||
|
|
63fe320b16 | ||
|
|
aafca303ad | ||
|
|
41821efd27 | ||
|
|
3a4082a1f3 | ||
|
|
b6fa44d003 | ||
|
|
09d2d7efc5 | ||
|
|
4c2ebf25fa | ||
|
|
b1b748dc9c | ||
|
|
cc3e4226d7 | ||
|
|
0f994d8136 | ||
|
|
298a9007ad | ||
|
|
b36e5d3372 | ||
|
|
d1b8eb10ce | ||
|
|
6000e7469e | ||
|
|
88d3fe65f3 | ||
|
|
558e7f877d | ||
|
|
f33d27f836 | ||
|
|
1694324261 | ||
|
|
3a3f5c50a8 | ||
|
|
b1abfd71c2 | ||
|
|
f5b7279225 | ||
|
|
b974e1bfd5 | ||
|
|
8dda68b3b9 | ||
|
|
33c24e0cb2 | ||
|
|
8fb0c5b8a8 | ||
|
|
d82122b624 | ||
|
|
f5966af95a | ||
|
|
9470ee1655 | ||
|
|
9a118cf637 | ||
|
|
d69757908f | ||
|
|
30525ef1c0 | ||
|
|
8414e72545 | ||
|
|
caca366511 | ||
|
|
261eb30951 | ||
|
|
bdb36ee296 | ||
|
|
1351f138fb | ||
|
|
8da51968dc | ||
|
|
30d23f15be | ||
|
|
0a718be622 | ||
|
|
21f258caa4 | ||
|
|
3584f83b30 | ||
|
|
056791233a | ||
|
|
dc435dcc6e | ||
|
|
6edbc9dd38 | ||
|
|
fd60d66c0d | ||
|
|
08ec89bbe1 | ||
|
|
836557f41c | ||
|
|
f7c5c6d344 | ||
|
|
9d18ad523e | ||
|
|
efcd7dcac2 | ||
|
|
768e87879e | ||
|
|
3c51cad614 | ||
|
|
bc642904e0 | ||
|
|
fa135036f4 | ||
|
|
2d414ec394 | ||
|
|
9e72df9c6c | ||
|
|
1a933e1c9a | ||
|
|
d5431f9843 | ||
|
|
e2dabc406d | ||
|
|
31f7f22629 | ||
|
|
29aaf430ca | ||
|
|
9ef3518a07 | ||
|
|
0b40bad986 | ||
|
|
34ff4d30f2 | ||
|
|
2b195f204d | ||
|
|
1d9596bf3d | ||
|
|
72d099d40a | ||
|
|
7ab6fe3baa | ||
|
|
198964df82 | ||
|
|
f0998d3686 | ||
|
|
75875ba9f5 | ||
|
|
ea009ff64b | ||
|
|
3c317f088b | ||
|
|
f91ee2ce3c | ||
|
|
98968d972f | ||
|
|
8ea264e96c | ||
|
|
5203cba5a7 | ||
|
|
f5fba12360 | ||
|
|
d7cc3ff8f1 | ||
|
|
4887cdc353 | ||
|
|
6aa38d2abc | ||
|
|
737e37f00e | ||
|
|
42bb72ab65 | ||
|
|
612ae4e3b5 | ||
|
|
27f9134912 | ||
|
|
c02718855d | ||
|
|
4f16222b31 | ||
|
|
8c27b34d0f | ||
|
|
0b71b54698 | ||
|
|
614b1322d5 | ||
|
|
eab335873e | ||
|
|
577dc9896d | ||
|
|
3a4bb4b9b2 | ||
|
|
c766915764 | ||
|
|
71c08648c6 | ||
|
|
95e2e6a5ac | ||
|
|
5cdf297d85 | ||
|
|
5d7137804a | ||
|
|
8b6b8fbd44 | ||
|
|
3e75aa260f | ||
|
|
92aca524a4 | ||
|
|
f70eff2e41 | ||
|
|
489c481acc | ||
|
|
3a1eaf375f | ||
|
|
52246dda28 | ||
|
|
3c200e2883 | ||
|
|
bda6505d5c | ||
|
|
a241c98837 | ||
|
|
12d7803044 | ||
|
|
d37a1acc9b | ||
|
|
7254571501 | ||
|
|
c300262804 | ||
|
|
e8ba57be90 | ||
|
|
15fad3da87 | ||
|
|
e2b0d3c368 | ||
|
|
3de85eb50e | ||
|
|
58e635c873 | ||
|
|
dde21d2337 | ||
|
|
e3fcbcb12b | ||
|
|
839296e3ba | ||
|
|
5b97b0e56a | ||
|
|
38ff2288da | ||
|
|
771a1ac2e6 | ||
|
|
f6fd6f535a | ||
|
|
f548ca5f82 | ||
|
|
616f51748e | ||
|
|
db5aaf9da6 | ||
|
|
a922032756 | ||
|
|
a415409a48 | ||
|
|
19d95b9014 | ||
|
|
73c7a8c147 | ||
|
|
4dc84bd64d | ||
|
|
dd96014f9b | ||
|
|
3cf2557af3 | ||
|
|
fcda0338cb | ||
|
|
ac19c81ef0 | ||
|
|
a83d57065f | ||
|
|
055ed32ab8 | ||
|
|
8d62165444 | ||
|
|
63bc7a7e79 | ||
|
|
f2b2501767 | ||
|
|
be1e2485ee | ||
|
|
38c4211649 | ||
|
|
71e6355c10 | ||
|
|
64411cdc02 | ||
|
|
9a2ff983a4 | ||
|
|
a522d4a411 | ||
|
|
9bdd77c277 | ||
|
|
cc68dddfe8 | ||
|
|
07ee7f8b21 | ||
|
|
15a355f08a | ||
|
|
c50486b611 | ||
|
|
edaca7a045 | ||
|
|
28432a50f0 | ||
|
|
8ab891fcff | ||
|
|
cab6df88ea | ||
|
|
42afd92f31 | ||
|
|
76d6b1721e | ||
|
|
7d562096d1 | ||
|
|
91c1aca0dd | ||
|
|
b8008a34fb | ||
|
|
482759ae72 | ||
|
|
b0d096d0ea | ||
|
|
e56ecfb7ae | ||
|
|
951bd134eb | ||
|
|
7ff04658f3 | ||
|
|
272f04dd32 | ||
|
|
29cb3796bf | ||
|
|
f51f9e75a9 | ||
|
|
63475784c7 | ||
|
|
1a7bb27370 | ||
|
|
4badaa4c85 | ||
|
|
bf6be964fd | ||
|
|
cdbcb0a512 | ||
|
|
f81cf193a2 | ||
|
|
cba56fcde6 | ||
|
|
72cbd13917 | ||
|
|
dc722f9724 | ||
|
|
1a35f32a48 | ||
|
|
65bd2753c2 | ||
|
|
570c9a9404 | ||
|
|
15151fe9ee | ||
|
|
2aad4caf9b | ||
|
|
289fda8c74 | ||
|
|
fd40778472 | ||
|
|
bc1641a68c | ||
|
|
5cf15d22d3 | ||
|
|
2b2a25daaa | ||
|
|
75a7f25642 | ||
|
|
8bab58f225 | ||
|
|
8ec006e02c | ||
|
|
2508dc6397 | ||
|
|
7670df35ad | ||
|
|
3b9782f942 | ||
|
|
3fca3489fb | ||
|
|
bb2d58eae0 |
3
.github/pull_request_template.md
vendored
3
.github/pull_request_template.md
vendored
@@ -1,9 +1,12 @@
|
||||
## What this Pull Request (PR) does
|
||||
|
||||
Please briefly describe what this PR does.
|
||||
|
||||
## Related issues
|
||||
|
||||
Please reference any open issues this PR relates to in here.
|
||||
If it closes an issue, type `closes #[ISSUE_NUMBER]`.
|
||||
|
||||
## Screenshots
|
||||
|
||||
Provide any screenshots you may find relevant to facilitate us understanding your PR.
|
||||
|
||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -20,13 +20,13 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: ./go.mod
|
||||
|
||||
|
||||
11
.github/workflows/patterns.yaml
vendored
11
.github/workflows/patterns.yaml
vendored
@@ -11,22 +11,27 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Verify Changes in Patterns Folder
|
||||
id: check-changes
|
||||
run: |
|
||||
git fetch origin
|
||||
if git diff --quiet HEAD~1 -- data/patterns; then
|
||||
echo "No changes detected in patterns folder."
|
||||
exit 1
|
||||
echo "changes=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Changes detected in patterns folder."
|
||||
echo "changes=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Zip the Patterns Folder
|
||||
if: steps.check-changes.outputs.changes == 'true'
|
||||
run: zip -r patterns.zip data/patterns/
|
||||
|
||||
- name: Upload Patterns Artifact
|
||||
if: steps.check-changes.outputs.changes == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: patterns
|
||||
|
||||
110
.github/workflows/release.yml
vendored
110
.github/workflows/release.yml
vendored
@@ -15,12 +15,12 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: ./go.mod
|
||||
|
||||
@@ -28,106 +28,30 @@ jobs:
|
||||
run: go test -v ./...
|
||||
|
||||
build:
|
||||
name: Build binaries for Windows, macOS, and Linux
|
||||
runs-on: ${{ matrix.os }}
|
||||
# only run in main upstream repo
|
||||
if: ${{ github.repository_owner == 'danielmiessler' }}
|
||||
name: Build & Release with Goreleaser
|
||||
needs: [test]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
arch: [amd64, arm64]
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: ./go.mod
|
||||
|
||||
- name: Determine OS Name
|
||||
id: os-name
|
||||
run: |
|
||||
if [ "${{ matrix.os }}" == "ubuntu-latest" ]; then
|
||||
echo "OS=linux" >> $GITHUB_ENV
|
||||
elif [ "${{ matrix.os }}" == "macos-latest" ]; then
|
||||
echo "OS=darwin" >> $GITHUB_ENV
|
||||
else
|
||||
echo "OS=windows" >> $GITHUB_ENV
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Build binary on Linux and macOS
|
||||
if: matrix.os != 'windows-latest'
|
||||
env:
|
||||
GOOS: ${{ env.OS }}
|
||||
GOARCH: ${{ matrix.arch }}
|
||||
run: |
|
||||
go build -o fabric-${OS}-${{ matrix.arch }} ./cmd/fabric
|
||||
|
||||
- name: Build binary on Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
env:
|
||||
GOOS: windows
|
||||
GOARCH: ${{ matrix.arch }}
|
||||
run: |
|
||||
go build -o fabric-windows-${{ matrix.arch }}.exe ./cmd/fabric
|
||||
|
||||
- name: Upload build artifact
|
||||
if: matrix.os != 'windows-latest'
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
name: fabric-${OS}-${{ matrix.arch }}
|
||||
path: fabric-${OS}-${{ matrix.arch }}
|
||||
|
||||
- name: Upload build artifact
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fabric-windows-${{ matrix.arch }}.exe
|
||||
path: fabric-windows-${{ matrix.arch }}.exe
|
||||
|
||||
- name: Get latest tag
|
||||
if: matrix.os != 'windows-latest'
|
||||
id: get_latest_tag
|
||||
run: |
|
||||
latest_tag=$(git tag --sort=-creatordate | head -n 1)
|
||||
echo "latest_tag=$latest_tag" >> $GITHUB_ENV
|
||||
|
||||
- name: Get latest tag
|
||||
if: matrix.os == 'windows-latest'
|
||||
id: get_latest_tag_windows
|
||||
run: |
|
||||
$latest_tag = git tag --sort=-creatordate | Select-Object -First 1
|
||||
Add-Content -Path $env:GITHUB_ENV -Value "latest_tag=$latest_tag"
|
||||
|
||||
- name: Create release if it doesn't exist
|
||||
shell: bash
|
||||
distribution: goreleaser
|
||||
args: release --clean
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
if ! gh release view ${{ env.latest_tag }} >/dev/null 2>&1; then
|
||||
gh release create ${{ env.latest_tag }} --title "Release ${{ env.latest_tag }}" --notes "Automated release for ${{ env.latest_tag }}"
|
||||
else
|
||||
echo "Release ${{ env.latest_tag }} already exists."
|
||||
fi
|
||||
|
||||
- name: Upload release artifact
|
||||
if: matrix.os == 'windows-latest'
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Update Release Description
|
||||
run: go run ./cmd/generate_changelog --release ${{ github.event.client_payload.tag || github.ref_name }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release upload ${{ env.latest_tag }} fabric-windows-${{ matrix.arch }}.exe
|
||||
|
||||
- name: Upload release artifact
|
||||
if: matrix.os != 'windows-latest'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release upload ${{ env.latest_tag }} fabric-${OS}-${{ matrix.arch }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -6,11 +6,12 @@ on:
|
||||
- main # Monitor the main branch
|
||||
paths-ignore:
|
||||
- "data/patterns/**"
|
||||
- "**/*.md"
|
||||
- "data/strategies/**"
|
||||
- "cmd/generate_changelog/*.db"
|
||||
- "cmd/generate_changelog/incoming/*.txt"
|
||||
- "scripts/pattern_descriptions/*.json"
|
||||
- "web/static/data/pattern_descriptions.json"
|
||||
- "**/*.md"
|
||||
|
||||
permissions:
|
||||
contents: write # Ensure the workflow has write permissions
|
||||
@@ -21,12 +22,13 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
update-version:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
if: >
|
||||
${{ github.repository_owner == 'danielmiessler' }} &&
|
||||
github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -48,12 +50,13 @@ jobs:
|
||||
run: |
|
||||
latest_tag=$(git tag --sort=-creatordate | head -n 1)
|
||||
echo "Latest tag is: $latest_tag"
|
||||
echo "tag=$latest_tag" >> $GITHUB_OUTPUT
|
||||
echo "tag=$latest_tag" >> $GITHUB_ENV # Save the latest tag to environment file
|
||||
|
||||
- name: Increment patch version
|
||||
id: increment_version
|
||||
run: |
|
||||
latest_tag=${{ env.tag }}
|
||||
latest_tag=${{ steps.get_latest_tag.outputs.tag }}
|
||||
major=$(echo "$latest_tag" | cut -d. -f1 | sed 's/v//')
|
||||
minor=$(echo "$latest_tag" | cut -d. -f2)
|
||||
patch=$(echo "$latest_tag" | cut -d. -f3)
|
||||
@@ -61,19 +64,21 @@ jobs:
|
||||
new_version="${major}.${minor}.${new_patch}"
|
||||
new_tag="v${new_version}"
|
||||
echo "New version is: $new_version"
|
||||
echo "new_version=$new_version" >> $GITHUB_OUTPUT
|
||||
echo "new_version=$new_version" >> $GITHUB_ENV # Save the new version to environment file
|
||||
echo "New tag is: $new_tag"
|
||||
echo "new_tag=$new_tag" >> $GITHUB_OUTPUT
|
||||
echo "new_tag=$new_tag" >> $GITHUB_ENV # Save the new tag to environment file
|
||||
|
||||
- name: Update version.go file
|
||||
run: |
|
||||
echo "package main" > cmd/fabric/version.go
|
||||
echo "" >> cmd/fabric/version.go
|
||||
echo "var version = \"${{ env.new_tag }}\"" >> cmd/fabric/version.go
|
||||
echo "var version = \"${{ steps.increment_version.outputs.new_tag }}\"" >> cmd/fabric/version.go
|
||||
|
||||
- name: Update version.nix file
|
||||
run: |
|
||||
echo "\"${{ env.new_version }}\"" > nix/pkgs/fabric/version.nix
|
||||
echo "\"${{ steps.increment_version.outputs.new_version }}\"" > nix/pkgs/fabric/version.nix
|
||||
|
||||
- name: Format source code
|
||||
run: |
|
||||
@@ -83,14 +88,24 @@ jobs:
|
||||
run: |
|
||||
nix run .#gomod2nix -- --outdir nix/pkgs/fabric
|
||||
|
||||
- name: Generate Changelog Entry
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
go run ./cmd/generate_changelog --process-prs ${{ steps.increment_version.outputs.new_tag }}
|
||||
go run ./cmd/generate_changelog --sync-db
|
||||
- name: Commit changes
|
||||
run: |
|
||||
# These files are modified by the version bump process
|
||||
git add cmd/fabric/version.go
|
||||
git add nix/pkgs/fabric/version.nix
|
||||
git add nix/pkgs/fabric/gomod2nix.toml
|
||||
git add .
|
||||
|
||||
# The changelog tool is responsible for staging CHANGELOG.md, changelog.db,
|
||||
# and removing the incoming/ directory.
|
||||
|
||||
if ! git diff --staged --quiet; then
|
||||
git commit -m "Update version to ${{ env.new_tag }} and commit $commit_hash"
|
||||
git commit -m "chore(release): Update version to ${{ steps.increment_version.outputs.new_tag }}"
|
||||
else
|
||||
echo "No changes to commit."
|
||||
fi
|
||||
@@ -103,10 +118,10 @@ jobs:
|
||||
|
||||
- name: Create a new tag
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.TAG_PAT }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
git tag ${{ env.new_tag }}
|
||||
git push origin ${{ env.new_tag }} # Push the new tag
|
||||
git tag ${{ steps.increment_version.outputs.new_tag }}
|
||||
git push origin ${{ steps.increment_version.outputs.new_tag }} # Push the new tag
|
||||
|
||||
- name: Dispatch event to trigger release workflow
|
||||
env:
|
||||
@@ -116,4 +131,4 @@ jobs:
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/${{ github.repository }}/dispatches \
|
||||
-d '{"event_type": "tag_created", "client_payload": {"tag": "${{ env.new_tag }}"}}'
|
||||
-d '{"event_type": "tag_created", "client_payload": {"tag": "${{ steps.increment_version.outputs.new_tag }}"}}'
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -350,3 +350,6 @@ web/static/*.png
|
||||
# Local tmp directory
|
||||
.tmp/
|
||||
tmp/
|
||||
|
||||
# Ignore .claude/
|
||||
.claude/
|
||||
|
||||
36
.goreleaser.yaml
Normal file
36
.goreleaser.yaml
Normal file
@@ -0,0 +1,36 @@
|
||||
# Read the documentation at https://goreleaser.com
|
||||
|
||||
version: 2
|
||||
|
||||
project_name: fabric
|
||||
before:
|
||||
hooks:
|
||||
# You may remove this if you don't use go modules.
|
||||
- go mod tidy
|
||||
# you may remove this if you don't need go generate
|
||||
# - go generate ./...
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
main: ./cmd/fabric
|
||||
binary: fabric
|
||||
|
||||
archives:
|
||||
- formats: [tar.gz]
|
||||
# this name template makes the OS and Arch compatible with the results of `uname`.
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_
|
||||
{{- title .Os }}_
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "386" }}i386
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
{{- if .Arm }}v{{ .Arm }}{{ end }}
|
||||
# use zip for windows archives
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
formats: [zip]
|
||||
68
.vscode/settings.json
vendored
68
.vscode/settings.json
vendored
@@ -1,33 +1,53 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"Achird",
|
||||
"addextension",
|
||||
"adduser",
|
||||
"AIML",
|
||||
"Anki",
|
||||
"anthropics",
|
||||
"Aoede",
|
||||
"atotto",
|
||||
"Autonoe",
|
||||
"badfile",
|
||||
"Behrens",
|
||||
"blindspots",
|
||||
"Bombal",
|
||||
"Buildx",
|
||||
"Callirhoe",
|
||||
"Callirrhoe",
|
||||
"Cerebras",
|
||||
"compadd",
|
||||
"compdef",
|
||||
"compinit",
|
||||
"creatordate",
|
||||
"curcontext",
|
||||
"custompatterns",
|
||||
"danielmiessler",
|
||||
"davidanson",
|
||||
"Debugf",
|
||||
"debuglog",
|
||||
"dedup",
|
||||
"deepseek",
|
||||
"Despina",
|
||||
"direnv",
|
||||
"DMARC",
|
||||
"DOCKERHUB",
|
||||
"dryrun",
|
||||
"dsrp",
|
||||
"editability",
|
||||
"Eisler",
|
||||
"elif",
|
||||
"Elister",
|
||||
"envrc",
|
||||
"Erinome",
|
||||
"Errorf",
|
||||
"eugeis",
|
||||
"Eugen",
|
||||
"excalidraw",
|
||||
"exolab",
|
||||
"fabriclogo",
|
||||
"flac",
|
||||
"fpath",
|
||||
"frequencypenalty",
|
||||
"fsdb",
|
||||
@@ -39,11 +59,13 @@
|
||||
"godotenv",
|
||||
"gofmt",
|
||||
"goimports",
|
||||
"golint",
|
||||
"gomod",
|
||||
"gonic",
|
||||
"goopenai",
|
||||
"GOPATH",
|
||||
"gopkg",
|
||||
"Goreleaser",
|
||||
"GOROOT",
|
||||
"Graphviz",
|
||||
"grokai",
|
||||
@@ -53,15 +75,22 @@
|
||||
"hasura",
|
||||
"hormozi",
|
||||
"Hormozi's",
|
||||
"horts",
|
||||
"HTMLURL",
|
||||
"imagetools",
|
||||
"jaredmontoya",
|
||||
"jessevdk",
|
||||
"Jina",
|
||||
"joho",
|
||||
"kballard",
|
||||
"Keploy",
|
||||
"Kore",
|
||||
"ksylvan",
|
||||
"Langdock",
|
||||
"Laomedeia",
|
||||
"ldflags",
|
||||
"libexec",
|
||||
"libnotify",
|
||||
"listcontexts",
|
||||
"listextensions",
|
||||
"listmodels",
|
||||
@@ -74,12 +103,23 @@
|
||||
"markmap",
|
||||
"matplotlib",
|
||||
"mattn",
|
||||
"mbed",
|
||||
"metacharacters",
|
||||
"Miessler",
|
||||
"modeline",
|
||||
"modelines",
|
||||
"mpga",
|
||||
"nometa",
|
||||
"numpy",
|
||||
"ollama",
|
||||
"ollamaapi",
|
||||
"Omri",
|
||||
"openaiapi",
|
||||
"opencode",
|
||||
"opencontainers",
|
||||
"openrouter",
|
||||
"Orus",
|
||||
"osascript",
|
||||
"otiai",
|
||||
"pdflatex",
|
||||
"pipx",
|
||||
@@ -88,38 +128,62 @@
|
||||
"presencepenalty",
|
||||
"printcontext",
|
||||
"printsession",
|
||||
"Pulcherrima",
|
||||
"pycache",
|
||||
"pyperclip",
|
||||
"readystream",
|
||||
"restapi",
|
||||
"rmextension",
|
||||
"Sadachbia",
|
||||
"Sadaltager",
|
||||
"samber",
|
||||
"sashabaranov",
|
||||
"sdist",
|
||||
"seaborn",
|
||||
"semgrep",
|
||||
"sess",
|
||||
"sgaunet",
|
||||
"shellquote",
|
||||
"SSEHTTP",
|
||||
"storer",
|
||||
"Streamlit",
|
||||
"stretchr",
|
||||
"subchunk",
|
||||
"Sulafat",
|
||||
"talkpanel",
|
||||
"Telos",
|
||||
"testpattern",
|
||||
"testuser",
|
||||
"Thacker",
|
||||
"tidwall",
|
||||
"topp",
|
||||
"ttrc",
|
||||
"unalias",
|
||||
"unconfigured",
|
||||
"unmarshalling",
|
||||
"updatepatterns",
|
||||
"useb",
|
||||
"USERPROFILE",
|
||||
"videoid",
|
||||
"webp",
|
||||
"WEBVTT",
|
||||
"winget",
|
||||
"wipecontext",
|
||||
"wipesession",
|
||||
"wireframes",
|
||||
"Worktree",
|
||||
"writeups",
|
||||
"xclip",
|
||||
"yourpatternname"
|
||||
"yourpatternname",
|
||||
"youtu",
|
||||
"YTDLP"
|
||||
],
|
||||
"cSpell.ignorePaths": [
|
||||
"go.mod",
|
||||
".gitignore",
|
||||
"CHANGELOG.md",
|
||||
"./scripts/installer/install.*"
|
||||
],
|
||||
"cSpell.ignorePaths": ["go.mod", ".gitignore", "CHANGELOG.md"],
|
||||
"markdownlint.config": {
|
||||
"MD004": false,
|
||||
"MD011": false,
|
||||
|
||||
548
CHANGELOG.md
548
CHANGELOG.md
@@ -1,5 +1,553 @@
|
||||
# Changelog
|
||||
|
||||
## v1.4.307 (2025-09-01)
|
||||
|
||||
### PR [#1745](https://github.com/danielmiessler/Fabric/pull/1745) by [ksylvan](https://github.com/ksylvan): Fabric Installation Improvements and Automated Release Updates
|
||||
|
||||
- Streamlined install process with one-line installer scripts and updated documentation
|
||||
- Added bash installer script for Unix systems
|
||||
- Added PowerShell installer script for Windows
|
||||
- Created installer documentation with usage examples
|
||||
- Simplified README installation with one-line installers
|
||||
|
||||
## v1.4.306 (2025-09-01)
|
||||
|
||||
### PR [#1742](https://github.com/danielmiessler/Fabric/pull/1742) by [ksylvan](https://github.com/ksylvan): Documentation and Pattern Updates
|
||||
|
||||
- Add winget installation method for Windows users
|
||||
- Include Docker Hub and GHCR image references with docker run examples
|
||||
- Remove deprecated PowerShell download link and unused show_fabric_options_markmap pattern
|
||||
- Update suggest_pattern with new AI patterns
|
||||
- Add personal development patterns for storytelling
|
||||
|
||||
## v1.4.305 (2025-08-31)
|
||||
|
||||
### PR [#1741](https://github.com/danielmiessler/Fabric/pull/1741) by [ksylvan](https://github.com/ksylvan): CI: Fix Release Description Update
|
||||
|
||||
- Fix: update release workflow to support manual dispatch with custom tag
|
||||
- Support custom tag from client payload in workflow
|
||||
- Fallback to github.ref_name when no custom tag provided
|
||||
- Enable manual release triggers with specified tag parameter
|
||||
|
||||
## v1.4.304 (2025-08-31)
|
||||
|
||||
### PR [#1740](https://github.com/danielmiessler/Fabric/pull/1740) by [ksylvan](https://github.com/ksylvan): Restore our custom Changelog Updates in GitHub Actions
|
||||
|
||||
- Add changelog generation step to GitHub release workflow
|
||||
- Create updateReleaseForRepo helper method for release updates
|
||||
- Add fork detection logic in UpdateReleaseDescription method
|
||||
- Implement upstream repository release update for forks
|
||||
- Enhance error handling with detailed repository context
|
||||
|
||||
## v1.4.303 (2025-08-28)
|
||||
|
||||
### PR [#1736](https://github.com/danielmiessler/Fabric/pull/1736) by [tonymet](https://github.com/tonymet): Winget Publishing and GoReleaser
|
||||
|
||||
- Added GoReleaser support for improved package distribution
|
||||
- Winget and Docker publishing moved to ksylvan/fabric-packager GitHub repo
|
||||
- Hardened release pipeline by gating workflows to upstream owner only
|
||||
- Migrated from custom tokens to built-in GITHUB_TOKEN for enhanced security
|
||||
- Removed docker-publish-on-tag workflow to reduce duplication and complexity
|
||||
- Added ARM binary release support with updated documentation
|
||||
|
||||
## v1.4.302 (2025-08-28)
|
||||
|
||||
### PR [#1737](https://github.com/danielmiessler/Fabric/pull/1737) by [ksylvan](https://github.com/ksylvan) and [OmriH-Elister](https://github.com/OmriH-Elister): Add New Psychological Analysis Patterns + devalue version bump
|
||||
|
||||
- Add create_story_about_person system pattern with narrative workflow
|
||||
- Add heal_person system pattern for compassionate healing plans
|
||||
- Update pattern_explanations to register new patterns and renumber indices
|
||||
- Extend pattern_descriptions with entries, tags, and concise descriptions
|
||||
- Bump devalue dependency from 5.1.1 to 5.3.2
|
||||
|
||||
## v1.4.301 (2025-08-28)
|
||||
|
||||
### PR [#1735](https://github.com/danielmiessler/Fabric/pull/1735) by [ksylvan](https://github.com/ksylvan): Fix Docker Build Path Configuration
|
||||
|
||||
- Fix: update Docker workflow to use specific Dockerfile and monitor markdown file changes
|
||||
- Add explicit Dockerfile path to Docker build action
|
||||
- Remove markdown files from workflow paths-ignore filter
|
||||
- Enable CI triggers for documentation file changes
|
||||
- Specify Docker build context with custom file location
|
||||
|
||||
## v1.4.300 (2025-08-28)
|
||||
|
||||
### PR [#1732](https://github.com/danielmiessler/Fabric/pull/1732) by [ksylvan](https://github.com/ksylvan): CI Infra: Changelog Generation Tool + Docker Image Pubishing
|
||||
|
||||
- Add GitHub Actions workflow to publish Docker images on tags
|
||||
- Build multi-arch images with Buildx and QEMU across amd64, arm64
|
||||
- Tag images using semver; push to GHCR and Docker Hub
|
||||
- Gate patterns workflow steps on detected changes instead of failing
|
||||
- Auto-detect GitHub owner and repo from git remote URL
|
||||
|
||||
## v1.4.299 (2025-08-27)
|
||||
|
||||
### PR [#1731](https://github.com/danielmiessler/Fabric/pull/1731) by [ksylvan](https://github.com/ksylvan): chore: upgrade ollama dependency from v0.9.0 to v0.11.7
|
||||
|
||||
- Updated ollama package from version 0.9.0 to 0.11.7
|
||||
- Fixed 8 security vulnerabilities including 5 high-severity CVEs that could cause denial of service attacks
|
||||
- Patched Ollama server vulnerabilities related to division by zero errors and memory exhaustion
|
||||
- Resolved security flaws that allowed malicious GGUF model file uploads to crash the server
|
||||
- Enhanced system stability and security posture through comprehensive dependency upgrade
|
||||
|
||||
## v1.4.298 (2025-08-27)
|
||||
|
||||
### PR [#1730](https://github.com/danielmiessler/Fabric/pull/1730) by [ksylvan](https://github.com/ksylvan): Modernize Dockerfile with Best Practices Implementation
|
||||
|
||||
- Remove docker-test framework and simplify production docker setup by eliminating complex testing infrastructure
|
||||
- Delete entire docker-test directory including test runner scripts and environment configuration files
|
||||
- Implement multi-stage build optimization in production Dockerfile to improve build efficiency
|
||||
- Remove docker-compose.yml and start-docker.sh helper scripts to streamline container workflow
|
||||
- Update README documentation with cleaner Docker usage instructions and reduced image size benefits
|
||||
|
||||
## v1.4.297 (2025-08-26)
|
||||
|
||||
### PR [#1729](https://github.com/danielmiessler/Fabric/pull/1729) by [ksylvan](https://github.com/ksylvan): Add GitHub Community Health Documents
|
||||
|
||||
- Add CODE_OF_CONDUCT defining respectful, collaborative community behavior
|
||||
- Add CONTRIBUTING with setup, testing, PR, changelog requirements
|
||||
- Add SECURITY policy with reporting process and response timelines
|
||||
- Add SUPPORT guide for bugs, features, discussions, expectations
|
||||
- Add docs README indexing guides, quick starts, contributor essentials
|
||||
|
||||
## v1.4.296 (2025-08-26)
|
||||
|
||||
### PR [#1728](https://github.com/danielmiessler/Fabric/pull/1728) by [ksylvan](https://github.com/ksylvan): Refactor Logging System to Use Centralized Debug Logger
|
||||
|
||||
- Replace fmt.Fprintf/os.Stderr with centralized debuglog.Log across CLI and add unconditional Log function for important messages
|
||||
- Improve OAuth flow messaging and token refresh diagnostics with better error handling
|
||||
- Update tests to capture debuglog output via SetOutput for better test coverage
|
||||
- Convert Perplexity streaming errors to unified debug logging and emit file write notifications through debuglog
|
||||
- Standardize extension registry warnings and announce large audio processing steps via centralized logger
|
||||
|
||||
## v1.4.295 (2025-08-24)
|
||||
|
||||
### PR [#1727](https://github.com/danielmiessler/Fabric/pull/1727) by [ksylvan](https://github.com/ksylvan): Standardize Anthropic Beta Failure Logging
|
||||
|
||||
- Refactor: route Anthropic beta failure logs through internal debug logger
|
||||
- Replace fmt.Fprintf stderr with debuglog.Debug for beta failures
|
||||
- Import internal log package and remove os dependency
|
||||
- Standardize logging level to debuglog.Basic for beta errors
|
||||
- Preserve fallback stream behavior when beta features fail
|
||||
|
||||
## v1.4.294 (2025-08-20)
|
||||
|
||||
### PR [#1723](https://github.com/danielmiessler/Fabric/pull/1723) by [ksylvan](https://github.com/ksylvan): docs: update README with Venice AI provider and Windows install script
|
||||
|
||||
- Add Venice AI provider configuration with API endpoint
|
||||
- Document Venice AI as privacy-first open-source provider
|
||||
- Include PowerShell installation script for Windows users
|
||||
- Add debug levels section to table of contents
|
||||
- Update recent major features with v1.4.294 release notes
|
||||
|
||||
## v1.4.293 (2025-08-19)
|
||||
|
||||
### PR [#1718](https://github.com/danielmiessler/Fabric/pull/1718) by [ksylvan](https://github.com/ksylvan): Implement Configurable Debug Logging Levels
|
||||
|
||||
- Add --debug flag controlling runtime logging verbosity levels
|
||||
- Introduce internal/log package with Off, Basic, Detailed, Trace
|
||||
- Replace ad-hoc Debugf and globals with centralized debug logger
|
||||
- Wire debug level during early CLI argument parsing
|
||||
- Add bash, zsh, fish completions for --debug levels
|
||||
|
||||
## v1.4.292 (2025-08-18)
|
||||
|
||||
### PR [#1717](https://github.com/danielmiessler/Fabric/pull/1717) by [ksylvan](https://github.com/ksylvan): Highlight default vendor/model in model listing
|
||||
|
||||
- Update PrintWithVendor signature to accept default vendor and model
|
||||
- Mark default vendor/model with asterisk in non-shell output
|
||||
- Compare vendor and model case-insensitively when marking
|
||||
- Pass registry defaults to PrintWithVendor from CLI
|
||||
- Add test ensuring default selection appears with asterisk
|
||||
### Direct commits
|
||||
|
||||
- Docs: update version number in README updates section from v1.4.290 to v1.4.291
|
||||
|
||||
## v1.4.291 (2025-08-18)
|
||||
|
||||
### PR [#1715](https://github.com/danielmiessler/Fabric/pull/1715) by [ksylvan](https://github.com/ksylvan): feat: add speech-to-text via OpenAI with transcription flags and comp…
|
||||
|
||||
- Add --transcribe-file flag to transcribe audio or video
|
||||
- Add --transcribe-model flag with model listing and completion
|
||||
- Add --split-media-file flag to chunk files over 25MB
|
||||
- Implement OpenAI transcription using Whisper and GPT-4o Transcribe
|
||||
- Integrate transcription pipeline into CLI before readability processing
|
||||
|
||||
## v1.4.290 (2025-08-17)
|
||||
|
||||
### PR [#1714](https://github.com/danielmiessler/Fabric/pull/1714) by [ksylvan](https://github.com/ksylvan): feat: add per-pattern model mapping support via environment variables
|
||||
|
||||
- Add per-pattern model mapping support via environment variables
|
||||
- Implement environment variable lookup for pattern-specific models
|
||||
- Support vendor|model format in environment variable specification
|
||||
- Enable shell startup file configuration for patterns
|
||||
- Transform pattern names to uppercase environment variable format
|
||||
|
||||
## v1.4.289 (2025-08-16)
|
||||
|
||||
### PR [#1710](https://github.com/danielmiessler/Fabric/pull/1710) by [ksylvan](https://github.com/ksylvan): feat: add --no-variable-replacement flag to disable pattern variable …
|
||||
|
||||
- Add --no-variable-replacement flag to disable pattern variable substitution
|
||||
- Introduce CLI flag to skip pattern variable replacement and wire it into domain request and session builder
|
||||
- Provide PatternsEntity.GetWithoutVariables for input-only pattern processing support
|
||||
- Refactor patterns code into reusable load and apply helpers
|
||||
- Update bash, zsh, fish completions with new flag and document in README and CLI help output
|
||||
|
||||
## v1.4.288 (2025-08-16)
|
||||
|
||||
### PR [#1709](https://github.com/danielmiessler/Fabric/pull/1709) by [ksylvan](https://github.com/ksylvan): Enhanced YouTube Subtitle Language Fallback Handling
|
||||
|
||||
- Fix: improve YouTube subtitle language fallback handling in yt-dlp integration
|
||||
- Fix typo "Gemmini" to "Gemini" in README
|
||||
- Add "kballard" and "shellquote" to VSCode dictionary
|
||||
- Add "YTDLP" to VSCode spell checker
|
||||
- Enhance subtitle language options with fallback variants
|
||||
|
||||
## v1.4.287 (2025-08-14)
|
||||
|
||||
### PR [#1706](https://github.com/danielmiessler/Fabric/pull/1706) by [ksylvan](https://github.com/ksylvan): Gemini Thinking Support and README (New Features) automation
|
||||
|
||||
- Add comprehensive "Recent Major Features" section to README
|
||||
- Introduce new readme_updates Python script for automation
|
||||
- Enable Gemini thinking configuration with token budgets
|
||||
- Update CLI help text for Gemini thinking support
|
||||
- Add comprehensive test coverage for Gemini thinking
|
||||
|
||||
## v1.4.286 (2025-08-14)
|
||||
|
||||
### PR [#1700](https://github.com/danielmiessler/Fabric/pull/1700) by [ksylvan](https://github.com/ksylvan): Introduce Thinking Config Across Anthropic and OpenAI Providers
|
||||
|
||||
- Add --thinking CLI flag for configurable reasoning levels across providers
|
||||
- Implement Anthropic ThinkingConfig with standardized budgets and tokens
|
||||
- Map OpenAI reasoning effort from thinking levels
|
||||
- Show thinking level in dry-run formatted options
|
||||
- Overhaul suggest_pattern docs with categories, workflows, usage examples
|
||||
|
||||
## v1.4.285 (2025-08-13)
|
||||
|
||||
### PR [#1698](https://github.com/danielmiessler/Fabric/pull/1698) by [ksylvan](https://github.com/ksylvan): Enable One Million Token Context Beta Feature for Sonnet-4
|
||||
|
||||
- Chore: upgrade anthropic-sdk-go to v1.9.1 and add beta feature support for context-1m
|
||||
- Add modelBetas map for beta feature configuration
|
||||
- Implement context-1m-2025-08-07 beta for Claude Sonnet 4
|
||||
- Add beta header support with fallback handling
|
||||
- Preserve existing beta headers in OAuth transport
|
||||
|
||||
## v1.4.284 (2025-08-12)
|
||||
|
||||
### PR [#1695](https://github.com/danielmiessler/Fabric/pull/1695) by [ksylvan](https://github.com/ksylvan): Introduce One-Liner Curl Install for Completions
|
||||
|
||||
- Add one-liner curl install method for shell completions without requiring repository cloning
|
||||
- Support downloading completions when files are missing locally with dry-run option for previewing changes
|
||||
- Enable custom download source via environment variable and create temporary directory for downloaded completion files
|
||||
- Add automatic cleanup of temporary files and validate downloaded files are non-empty and not HTML
|
||||
- Improve error handling and standardize logging by routing informational messages to stderr to avoid stdout pollution
|
||||
|
||||
## v1.4.283 (2025-08-12)
|
||||
|
||||
### PR [#1692](https://github.com/danielmiessler/Fabric/pull/1692) by [ksylvan](https://github.com/ksylvan): Add Vendor Selection Support for Models
|
||||
|
||||
- Add -V/--vendor flag to specify model vendor
|
||||
- Implement vendor-aware model resolution and availability validation
|
||||
- Warn on ambiguous models; suggest --vendor to disambiguate
|
||||
- Update bash, zsh, fish completions with vendor suggestions
|
||||
- Extend --listmodels to print vendor|model when interactive
|
||||
|
||||
## v1.4.282 (2025-08-11)
|
||||
|
||||
### PR [#1689](https://github.com/danielmiessler/Fabric/pull/1689) by [ksylvan](https://github.com/ksylvan): Enhanced Shell Completions for Fabric CLI Binaries
|
||||
|
||||
- Add 'fabric-ai' alias support across all shell completions
|
||||
- Use invoked command name for dynamic completion list queries
|
||||
- Refactor fish completions into reusable registrar for multiple commands
|
||||
- Update Bash completion to reference executable via COMP_WORDS[0]
|
||||
- Install completions automatically with new cross-shell setup script
|
||||
|
||||
## v1.4.281 (2025-08-11)
|
||||
|
||||
### PR [#1687](https://github.com/danielmiessler/Fabric/pull/1687) by [ksylvan](https://github.com/ksylvan): Add Web Search Tool Support for Gemini Models
|
||||
|
||||
- Enable Gemini models to use web search tool with --search flag
|
||||
- Add validation for search-location timezone and language code formats
|
||||
- Normalize language codes from underscores to hyphenated form
|
||||
- Append deduplicated web citations under standardized Sources section
|
||||
- Improve robustness for nil candidates and content parts
|
||||
|
||||
## v1.4.280 (2025-08-10)
|
||||
|
||||
### PR [#1686](https://github.com/danielmiessler/Fabric/pull/1686) by [ksylvan](https://github.com/ksylvan): Prevent duplicate text output in OpenAI streaming responses
|
||||
|
||||
- Fix: prevent duplicate text output in OpenAI streaming responses
|
||||
- Skip processing of ResponseOutputTextDone events
|
||||
- Prevent doubled text in stream output
|
||||
- Add clarifying comment about API behavior
|
||||
- Maintain delta chunk streaming functionality
|
||||
|
||||
## v1.4.279 (2025-08-10)
|
||||
|
||||
### PR [#1685](https://github.com/danielmiessler/Fabric/pull/1685) by [ksylvan](https://github.com/ksylvan): Fix Gemini Role Mapping for API Compatibility
|
||||
|
||||
- Fix Gemini role mapping to ensure proper API compatibility by converting chat roles to Gemini's user/model format
|
||||
- Map assistant role to model role per Gemini API constraints
|
||||
- Map system, developer, function, and tool roles to user role for proper handling
|
||||
- Default unrecognized roles to user role to preserve instruction context
|
||||
- Add comprehensive unit tests to validate convertMessages role mapping logic
|
||||
|
||||
## v1.4.278 (2025-08-09)
|
||||
|
||||
### PR [#1681](https://github.com/danielmiessler/Fabric/pull/1681) by [ksylvan](https://github.com/ksylvan): Enhance YouTube Support with Custom yt-dlp Arguments
|
||||
|
||||
- Add `--yt-dlp-args` flag for custom YouTube downloader options with advanced control capabilities
|
||||
- Implement smart subtitle language fallback system when requested locale is unavailable
|
||||
- Add fallback logic for YouTube subtitle language detection with auto-detection of downloaded languages
|
||||
- Replace custom argument parser with shellquote and precompile regexes for improved performance and safety
|
||||
|
||||
## v1.4.277 (2025-08-08)
|
||||
|
||||
### PR [#1679](https://github.com/danielmiessler/Fabric/pull/1679) by [ksylvan](https://github.com/ksylvan): Add cross-platform desktop notifications to Fabric CLI
|
||||
|
||||
- Add cross-platform desktop notifications with secure custom commands
|
||||
- Integrate notification sending into chat processing workflow
|
||||
- Add --notification and --notification-command CLI flags and help
|
||||
- Provide cross-platform providers: macOS, Linux, Windows with fallbacks
|
||||
- Escape shell metacharacters to prevent injection vulnerabilities
|
||||
|
||||
## v1.4.276 (2025-08-08)
|
||||
|
||||
### Direct commits
|
||||
|
||||
- Ci: add write permissions to update_release_notes job
|
||||
|
||||
- Add contents write permission to release notes job
|
||||
|
||||
- Enable GitHub Actions to modify repository contents
|
||||
- Fix potential permission issues during release process
|
||||
|
||||
## v1.4.275 (2025-08-07)
|
||||
|
||||
### PR [#1676](https://github.com/danielmiessler/Fabric/pull/1676) by [ksylvan](https://github.com/ksylvan): Refactor authentication to support GITHUB_TOKEN and GH_TOKEN
|
||||
|
||||
- Refactor: centralize GitHub token retrieval logic into utility function
|
||||
- Support both GITHUB_TOKEN and GH_TOKEN environment variables with fallback handling
|
||||
- Add new util/token.go file for centralized token handling across the application
|
||||
- Update walker.go and main.go to use the new centralized token utility function
|
||||
- Feat: add 'gpt-5' to raw-mode models in OpenAI client to bypass structured chat message formatting
|
||||
|
||||
## v1.4.274 (2025-08-07)
|
||||
|
||||
### PR [#1673](https://github.com/danielmiessler/Fabric/pull/1673) by [ksylvan](https://github.com/ksylvan): Add Support for Claude Opus 4.1 Model
|
||||
|
||||
- Add Claude Opus 4.1 model support
|
||||
- Upgrade anthropic-sdk-go from v1.4.0 to v1.7.0
|
||||
- Fix temperature/topP parameter conflict for models
|
||||
- Refactor release workflow to use shared version job and simplify OS handling
|
||||
- Improve chat parameter defaults handling with domain constants
|
||||
|
||||
## v1.4.273 (2025-08-05)
|
||||
|
||||
### Direct commits
|
||||
|
||||
- Remove redundant words from codebase
|
||||
- Fix typos in t_ patterns
|
||||
|
||||
## v1.4.272 (2025-07-28)
|
||||
|
||||
### PR [#1658](https://github.com/danielmiessler/Fabric/pull/1658) by [ksylvan](https://github.com/ksylvan): Update Release Process for Data Consistency
|
||||
|
||||
- Add database sync before generating changelog in release workflow
|
||||
- Ensure changelog generation includes latest database updates
|
||||
- Update changelog cache database
|
||||
|
||||
## v1.4.271 (2025-07-28)
|
||||
|
||||
### PR [#1657](https://github.com/danielmiessler/Fabric/pull/1657) by [ksylvan](https://github.com/ksylvan): Add GitHub Release Description Update Feature
|
||||
|
||||
- Add GitHub release description update via `--release` flag
|
||||
- Implement `ReleaseManager` for managing release descriptions
|
||||
- Create `release.go` for handling release updates
|
||||
- Update `release.yml` to run changelog generation
|
||||
- Enable AI summary updates for GitHub releases
|
||||
|
||||
## v1.4.270 (2025-07-27)
|
||||
|
||||
### PR [#1654](https://github.com/danielmiessler/Fabric/pull/1654) by [ksylvan](https://github.com/ksylvan): Refine Output File Handling for Safety
|
||||
|
||||
- Fix: prevent file overwrite and improve output messaging in CreateOutputFile
|
||||
- Add file existence check before creating output file
|
||||
- Return error if target file already exists
|
||||
- Change success message to write to stderr
|
||||
- Update message format with brackets for clarity
|
||||
|
||||
## v1.4.269 (2025-07-26)
|
||||
|
||||
### PR [#1653](https://github.com/danielmiessler/Fabric/pull/1653) by [ksylvan](https://github.com/ksylvan): docs: update Gemini TTS model references to gemini-2.5-flash-preview-tts
|
||||
|
||||
- Updated Gemini TTS model references from gemini-2.0-flash-tts to gemini-2.5-flash-preview-tts throughout documentation
|
||||
- Modified documentation examples to use the new gemini-2.5-flash-preview-tts model
|
||||
- Updated voice selection example commands in Gemini-TTS.md
|
||||
- Revised CLI help text example commands to reflect model changes
|
||||
- Updated changelog database binary file
|
||||
|
||||
## v1.4.268 (2025-07-26)
|
||||
|
||||
### PR [#1652](https://github.com/danielmiessler/Fabric/pull/1652) by [ksylvan](https://github.com/ksylvan): Implement Voice Selection for Gemini Text-to-Speech
|
||||
|
||||
- Feat: add Gemini TTS voice selection and listing functionality
|
||||
- Add `--voice` flag for TTS voice selection
|
||||
- Add `--list-gemini-voices` command for voice discovery
|
||||
- Implement voice validation for Gemini TTS models
|
||||
- Update shell completions for voice options
|
||||
|
||||
## v1.4.267 (2025-07-26)
|
||||
|
||||
### PR [#1650](https://github.com/danielmiessler/Fabric/pull/1650) by [ksylvan](https://github.com/ksylvan): Update Gemini Plugin to New SDK with TTS Support
|
||||
|
||||
- Update Gemini SDK to new genai library and add TTS audio output support
|
||||
- Replace deprecated generative-ai-go with google.golang.org/genai library
|
||||
- Add TTS model detection and audio output validation
|
||||
- Implement WAV file generation for TTS audio responses
|
||||
- Add audio format checking utilities in CLI output
|
||||
|
||||
## v1.4.266 (2025-07-25)
|
||||
|
||||
### PR [#1649](https://github.com/danielmiessler/Fabric/pull/1649) by [ksylvan](https://github.com/ksylvan): Fix Conditional API Initialization to Prevent Unnecessary Error Messages
|
||||
|
||||
- Prevent unconfigured API initialization and add Docker test suite
|
||||
- Add BEDROCK_AWS_REGION requirement for Bedrock initialization
|
||||
- Implement IsConfigured check for Ollama API URL
|
||||
- Create comprehensive Docker testing environment with 6 scenarios
|
||||
- Add interactive test runner with shell access
|
||||
|
||||
## v1.4.265 (2025-07-25)
|
||||
|
||||
### PR [#1647](https://github.com/danielmiessler/Fabric/pull/1647) by [ksylvan](https://github.com/ksylvan): Simplify Workflow with Single Version Retrieval Step
|
||||
|
||||
- Replace git tag lookup with version.nix file reading for release workflow
|
||||
- Remove OS-specific git tag retrieval steps and add unified version extraction from nix file
|
||||
- Include version format validation with regex check
|
||||
- Add error handling for missing version file
|
||||
- Consolidate cross-platform version logic into single step with bash shell for consistent version parsing
|
||||
|
||||
## v1.4.264 (2025-07-22)
|
||||
|
||||
### PR [#1642](https://github.com/danielmiessler/Fabric/pull/1642) by [ksylvan](https://github.com/ksylvan): Add --sync-db to `generate_changelog`, plus many fixes
|
||||
|
||||
- Add database synchronization command with comprehensive validation and sync-db flag for database integrity validation
|
||||
- Implement version and commit existence checking methods with enhanced time parsing using RFC3339Nano fallback support
|
||||
- Improve timestamp handling and merge commit detection in changelog generator with comprehensive merge commit detection using parents
|
||||
- Add email field support to PRCommit struct for author information and improve error logging throughout changelog generation
|
||||
- Optimize merge pattern matching with lazy initialization and thread-safe pattern compilation for better performance
|
||||
|
||||
### Direct commits
|
||||
|
||||
- Chore: incoming 1642 changelog entry
|
||||
- Fix: improve error message formatting in version date parsing
|
||||
|
||||
- Add actual error details to date parsing failure message
|
||||
|
||||
- Include error variable in stderr output formatting
|
||||
- Enhance debugging information for invalid date formats
|
||||
- Docs: Update CHANGELOG after v1.4.263
|
||||
|
||||
## v1.4.263 (2025-07-21)
|
||||
|
||||
### PR [#1641](https://github.com/danielmiessler/Fabric/pull/1641) by [ksylvan](https://github.com/ksylvan): Fix Fabric Web timeout error
|
||||
|
||||
- Chore: extend proxy timeout in `vite.config.ts` to 15 minutes
|
||||
- Increase `/api` proxy timeout to 900,000 ms
|
||||
- Increase `/names` proxy timeout to 900,000 ms
|
||||
|
||||
## v1.4.262 (2025-07-21)
|
||||
|
||||
### PR [#1640](https://github.com/danielmiessler/Fabric/pull/1640) by [ksylvan](https://github.com/ksylvan): Implement Automated Changelog System for CI/CD Integration
|
||||
|
||||
- Add automated changelog processing for CI/CD integration with comprehensive test coverage and GitHub client validation methods
|
||||
- Implement release aggregation for incoming files with git operations for staging changes and support for version detection from nix files
|
||||
- Change push behavior from opt-out to opt-in with GitHub token authentication and automatic repository detection
|
||||
- Enhance changelog generation to avoid duplicate commit entries by extracting PR numbers and filtering commits already included via PR files
|
||||
- Add version parameter requirement for PR processing with commit SHA tracking to prevent duplicate entries and improve formatting consistency
|
||||
|
||||
### Direct commits
|
||||
|
||||
- Docs: Update CHANGELOG after v1.4.261
|
||||
|
||||
## v1.4.261 (2025-07-19)
|
||||
|
||||
### PR [#1637](https://github.com/danielmiessler/Fabric/pull/1637) by [ksylvan](https://github.com/ksylvan): chore: update `NeedsRawMode` to include `mistral` prefix for Ollama
|
||||
|
||||
- Updated `NeedsRawMode` to include `mistral` prefix for Ollama compatibility
|
||||
- Added `mistral` to `ollamaPrefixes` list for improved model support
|
||||
|
||||
### Direct commits
|
||||
|
||||
- Updated CHANGELOG after v1.4.260 release
|
||||
|
||||
## v1.4.260 (2025-07-18)
|
||||
|
||||
### PR [#1634](https://github.com/danielmiessler/Fabric/pull/1634) by [ksylvan](https://github.com/ksylvan): Fix abort in Exo-Labs provider plugin; with credit to @sakithahSenid
|
||||
|
||||
- Fix abort issue in Exo-Labs provider plugin
|
||||
- Add API key setup question to Exolab AI plugin configuration
|
||||
- Include API key setup question in Exolab client with required field validation
|
||||
- Add "openaiapi" to VSCode spell check dictionary
|
||||
- Maintain existing API base URL configuration order
|
||||
|
||||
### Direct commits
|
||||
|
||||
- Update CHANGELOG after v1.4.259
|
||||
|
||||
## v1.4.259 (2025-07-18)
|
||||
|
||||
### PR [#1633](https://github.com/danielmiessler/Fabric/pull/1633) by [ksylvan](https://github.com/ksylvan): YouTube VTT Processing Enhancement
|
||||
|
||||
- Fix: prevent duplicate segments in VTT file processing by adding deduplication map to track seen segments
|
||||
- Feat: enhance VTT duplicate filtering to allow legitimate repeated content with configurable time gap detection
|
||||
- Feat: improve timestamp parsing to handle fractional seconds and optional seconds/milliseconds formats
|
||||
- Chore: refactor timestamp regex to global scope and improve performance by avoiding repeated compilation
|
||||
- Fix: Youtube VTT parsing gap test and extract seconds parsing logic into reusable function
|
||||
|
||||
### Direct commits
|
||||
|
||||
- Docs: Update CHANGELOG after v1.4.258
|
||||
|
||||
## v1.4.258 (2025-07-17)
|
||||
|
||||
### PR [#1629](https://github.com/danielmiessler/Fabric/pull/1629) by [ksylvan](https://github.com/ksylvan): Create Default (empty) .env in ~/.config/fabric on Demand
|
||||
|
||||
- Add startup check to initialize config and .env file automatically
|
||||
- Introduce ensureEnvFile function to create ~/.config/fabric/.env if missing
|
||||
- Add directory creation for config path in ensureEnvFile
|
||||
- Integrate setup flag in CLI to call ensureEnvFile on demand
|
||||
- Improve error handling and permissions in ensureEnvFile function
|
||||
|
||||
### Direct commits
|
||||
|
||||
- Update README and CHANGELOG after v1.4.257
|
||||
|
||||
## v1.4.257 (2025-07-17)
|
||||
|
||||
### PR [#1628](https://github.com/danielmiessler/Fabric/pull/1628) by [ksylvan](https://github.com/ksylvan): Introduce CLI Flag to Disable OpenAI Responses API
|
||||
|
||||
- Add `--disable-responses-api` CLI flag for OpenAI control and llama-server compatibility
|
||||
- Implement `SetResponsesAPIEnabled` method in OpenAI client with configuration control
|
||||
- Update default config path to `~/.config/fabric/config.yaml`
|
||||
- Add CLI completions for new API flag across zsh, bash, and fish shells
|
||||
- Update CHANGELOG after v1.4.256 release
|
||||
|
||||
## v1.4.256 (2025-07-17)
|
||||
|
||||
### PR [#1624](https://github.com/danielmiessler/Fabric/pull/1624) by [ksylvan](https://github.com/ksylvan): Feature: Add Automatic ~/.fabric.yaml Config Detection
|
||||
|
||||
- Implement default ~/.fabric.yaml config file detection
|
||||
- Add support for short flag parsing with dashes
|
||||
- Improve dry run output formatting and config path error handling
|
||||
- Refactor dry run response construction into helper method
|
||||
- Extract flag parsing logic into separate extractFlag function
|
||||
|
||||
### Direct commits
|
||||
|
||||
- Docs: Update CHANGELOG after v1.4.255
|
||||
|
||||
## v1.4.255 (2025-07-16)
|
||||
|
||||
### Direct commits
|
||||
|
||||
188
README.md
188
README.md
@@ -47,6 +47,55 @@ It's all really exciting and powerful, but _it's not easy to integrate this func
|
||||
|
||||
Fabric organizes prompts by real-world task, allowing people to create, collect, and organize their most important AI solutions in a single place for use in their favorite tools. And if you're command-line focused, you can use Fabric itself as the interface!
|
||||
|
||||
## Updates
|
||||
|
||||
Dear Users,
|
||||
|
||||
We've been doing so many exciting things here at Fabric, I wanted to give a quick summary here to give you a sense of our development velocity!
|
||||
|
||||
Below are the **new features and capabilities** we've added (newest first):
|
||||
|
||||
### Recent Major Features
|
||||
|
||||
- [v1.4.303](https://github.com/danielmiessler/fabric/releases/tag/v1.4.303) (Aug 29, 2025) — **New Binary Releases**: Linux ARM and Windows ARM targets. You can run Fabric on the Raspberry PI and on your Windows Surface!
|
||||
- [v1.4.294](https://github.com/danielmiessler/fabric/releases/tag/v1.4.294) (Aug 20, 2025) — **Venice AI Support**: Added the Venice AI provider. Venice is a Privacy-First, Open-Source AI provider. See their ["About Venice"](https://docs.venice.ai/overview/about-venice) page for details.
|
||||
- [v1.4.291](https://github.com/danielmiessler/fabric/releases/tag/v1.4.291) (Aug 18, 2025) — **Speech To Text**: Add OpenAI speech-to-text support with `--transcribe-file`, `--transcribe-model`, and `--split-media-file` flags.
|
||||
- [v1.4.287](https://github.com/danielmiessler/fabric/releases/tag/v1.4.287) (Aug 16, 2025) — **AI Reasoning**: Add Thinking to Gemini models and introduce `readme_updates` python script
|
||||
- [v1.4.286](https://github.com/danielmiessler/fabric/releases/tag/v1.4.286) (Aug 14, 2025) — **AI Reasoning**: Introduce Thinking Config Across Anthropic and OpenAI Providers
|
||||
- [v1.4.285](https://github.com/danielmiessler/fabric/releases/tag/v1.4.285) (Aug 13, 2025) — **Extended Context**: Enable One Million Token Context Beta Feature for Sonnet-4
|
||||
- [v1.4.284](https://github.com/danielmiessler/fabric/releases/tag/v1.4.284) (Aug 12, 2025) — **Easy Shell Completions Setup**: Introduce One-Liner Curl Install for Completions
|
||||
- [v1.4.283](https://github.com/danielmiessler/fabric/releases/tag/v1.4.283) (Aug 12, 2025) — **Model Management**: Add Vendor Selection Support for Models
|
||||
- [v1.4.282](https://github.com/danielmiessler/fabric/releases/tag/v1.4.282) (Aug 11, 2025) — **Enhanced Shell Completions**: Enhanced Shell Completions for Fabric CLI Binaries
|
||||
- [v1.4.281](https://github.com/danielmiessler/fabric/releases/tag/v1.4.281) (Aug 11, 2025) — **Gemini Search Tool**: Add Web Search Tool Support for Gemini Models
|
||||
- [v1.4.278](https://github.com/danielmiessler/fabric/releases/tag/v1.4.278) (Aug 9, 2025) — **Enhance YouTube Transcripts**: Enhance YouTube Support with Custom yt-dlp Arguments
|
||||
- [v1.4.277](https://github.com/danielmiessler/fabric/releases/tag/v1.4.277) (Aug 8, 2025) — **Desktop Notifications**: Add cross-platform desktop notifications to Fabric CLI
|
||||
- [v1.4.274](https://github.com/danielmiessler/fabric/releases/tag/v1.4.274) (Aug 7, 2025) — **Claude 4.1 Added**: Add Support for Claude Opus 4.1 Model
|
||||
- [v1.4.271](https://github.com/danielmiessler/fabric/releases/tag/v1.4.271) (Jul 28, 2025) — **AI Summarized Release Notes**: Enable AI summary updates for GitHub releases
|
||||
- [v1.4.268](https://github.com/danielmiessler/fabric/releases/tag/v1.4.268) (Jul 26, 2025) — **Gemini TTS Voice Selection**: add Gemini TTS voice selection and listing functionality
|
||||
- [v1.4.267](https://github.com/danielmiessler/fabric/releases/tag/v1.4.267) (Jul 26, 2025) — **Text-to-Speech**: Update Gemini Plugin to New SDK with TTS Support
|
||||
- [v1.4.258](https://github.com/danielmiessler/fabric/releases/tag/v1.4.258) (Jul 17, 2025) — **Onboarding Improved**: Add startup check to initialize config and .env file automatically
|
||||
- [v1.4.257](https://github.com/danielmiessler/fabric/releases/tag/v1.4.257) (Jul 17, 2025) — **OpenAI Routing Control**: Introduce CLI Flag to Disable OpenAI Responses API
|
||||
- [v1.4.252](https://github.com/danielmiessler/fabric/releases/tag/v1.4.252) (Jul 16, 2025) — **Hide Thinking Block**: Optional Hiding of Model Thinking Process with Configurable Tags
|
||||
- [v1.4.246](https://github.com/danielmiessler/fabric/releases/tag/v1.4.246) (Jul 14, 2025) — **Automatic ChangeLog Updates**: Add AI-powered changelog generation with high-performance Go tool and comprehensive caching
|
||||
- [v1.4.245](https://github.com/danielmiessler/fabric/releases/tag/v1.4.245) (Jul 11, 2025) — **Together AI**: Together AI Support with OpenAI Fallback Mechanism Added
|
||||
- [v1.4.232](https://github.com/danielmiessler/fabric/releases/tag/v1.4.232) (Jul 6, 2025) — **Add Custom**: Add Custom Patterns Directory Support
|
||||
- [v1.4.231](https://github.com/danielmiessler/fabric/releases/tag/v1.4.231) (Jul 5, 2025) — **OAuth Auto-Auth**: OAuth Authentication Support for Anthropic (Use your Max Subscription)
|
||||
- [v1.4.230](https://github.com/danielmiessler/fabric/releases/tag/v1.4.230) (Jul 5, 2025) — **Model Management**: Add advanced image generation parameters for OpenAI models with four new CLI flags
|
||||
- [v1.4.227](https://github.com/danielmiessler/fabric/releases/tag/v1.4.227) (Jul 4, 2025) — **Add Image**: Add Image Generation Support to Fabric
|
||||
- [v1.4.226](https://github.com/danielmiessler/fabric/releases/tag/v1.4.226) (Jul 4, 2025) — **Web Search**: OpenAI Plugin Now Supports Web Search Functionality
|
||||
- [v1.4.225](https://github.com/danielmiessler/fabric/releases/tag/v1.4.225) (Jul 4, 2025) — **Web Search**: Runtime Web Search Control via Command-Line `--search` Flag
|
||||
- [v1.4.224](https://github.com/danielmiessler/fabric/releases/tag/v1.4.224) (Jul 1, 2025) — **Add code_review**: Add code_review pattern and updates in Pattern_Descriptions
|
||||
- [v1.4.222](https://github.com/danielmiessler/fabric/releases/tag/v1.4.222) (Jul 1, 2025) — **OpenAI Plugin**: OpenAI Plugin Migrates to New Responses API
|
||||
- [v1.4.218](https://github.com/danielmiessler/fabric/releases/tag/v1.4.218) (Jun 27, 2025) — **Model Management**: Add Support for OpenAI Search and Research Model Variants
|
||||
- [v1.4.217](https://github.com/danielmiessler/fabric/releases/tag/v1.4.217) (Jun 26, 2025) — **New YouTube**: New YouTube Transcript Endpoint Added to REST API
|
||||
- [v1.4.212](https://github.com/danielmiessler/fabric/releases/tag/v1.4.212) (Jun 23, 2025) — **Add Langdock**: Add Langdock AI and enhance generic OpenAI compatible support
|
||||
- [v1.4.211](https://github.com/danielmiessler/fabric/releases/tag/v1.4.211) (Jun 19, 2025) — **REST API**: REST API and Web UI Now Support Dynamic Pattern Variables
|
||||
- [v1.4.210](https://github.com/danielmiessler/fabric/releases/tag/v1.4.210) (Jun 18, 2025) — **Add Citations**: Add Citation Support to Perplexity Response
|
||||
- [v1.4.208](https://github.com/danielmiessler/fabric/releases/tag/v1.4.208) (Jun 17, 2025) — **Add Perplexity**: Add Perplexity AI Provider with Token Limits Support
|
||||
- [v1.4.203](https://github.com/danielmiessler/fabric/releases/tag/v1.4.203) (Jun 14, 2025) — **Add Amazon Bedrock**: Add support for Amazon Bedrock
|
||||
|
||||
These features represent our commitment to making Fabric the most powerful and flexible AI augmentation framework available!
|
||||
|
||||
## Intro videos
|
||||
|
||||
Keep in mind that many of these were recorded when Fabric was Python-based, so remember to use the current [install instructions](#installation) below.
|
||||
@@ -60,34 +109,37 @@ Keep in mind that many of these were recorded when Fabric was Python-based, so r
|
||||
|
||||
- [`fabric`](#fabric)
|
||||
- [What and why](#what-and-why)
|
||||
- [Updates](#updates)
|
||||
- [Recent Major Features](#recent-major-features)
|
||||
- [Intro videos](#intro-videos)
|
||||
- [Navigation](#navigation)
|
||||
- [Updates](#updates)
|
||||
- [Changelog](#changelog)
|
||||
- [Philosophy](#philosophy)
|
||||
- [Breaking problems into components](#breaking-problems-into-components)
|
||||
- [Too many prompts](#too-many-prompts)
|
||||
- [Installation](#installation)
|
||||
- [Get Latest Release Binaries](#get-latest-release-binaries)
|
||||
- [Windows](#windows)
|
||||
- [macOS (arm64)](#macos-arm64)
|
||||
- [macOS (amd64)](#macos-amd64)
|
||||
- [Linux (amd64)](#linux-amd64)
|
||||
- [Linux (arm64)](#linux-arm64)
|
||||
- [One-Line Install (Recommended)](#one-line-install-recommended)
|
||||
- [Manual Binary Downloads](#manual-binary-downloads)
|
||||
- [Using package managers](#using-package-managers)
|
||||
- [macOS (Homebrew)](#macos-homebrew)
|
||||
- [Arch Linux (AUR)](#arch-linux-aur)
|
||||
- [Windows](#windows)
|
||||
- [From Source](#from-source)
|
||||
- [Docker](#docker)
|
||||
- [Environment Variables](#environment-variables)
|
||||
- [Setup](#setup)
|
||||
- [Per-Pattern Model Mapping](#per-pattern-model-mapping)
|
||||
- [Add aliases for all patterns](#add-aliases-for-all-patterns)
|
||||
- [Save your files in markdown using aliases](#save-your-files-in-markdown-using-aliases)
|
||||
- [Migration](#migration)
|
||||
- [Upgrading](#upgrading)
|
||||
- [Shell Completions](#shell-completions)
|
||||
- [Quick install (no clone required)](#quick-install-no-clone-required)
|
||||
- [Zsh Completion](#zsh-completion)
|
||||
- [Bash Completion](#bash-completion)
|
||||
- [Fish Completion](#fish-completion)
|
||||
- [Usage](#usage)
|
||||
- [Debug Levels](#debug-levels)
|
||||
- [Our approach to prompting](#our-approach-to-prompting)
|
||||
- [Examples](#examples)
|
||||
- [Just use the Patterns](#just-use-the-patterns)
|
||||
@@ -111,7 +163,7 @@ Keep in mind that many of these were recorded when Fabric was Python-based, so r
|
||||
|
||||
<br />
|
||||
|
||||
## Updates
|
||||
## Changelog
|
||||
|
||||
Fabric is evolving rapidly.
|
||||
|
||||
@@ -150,29 +202,25 @@ Fabric has Patterns for all sorts of life and work activities, including:
|
||||
|
||||
## Installation
|
||||
|
||||
To install Fabric, you can use the latest release binaries or install it from the source.
|
||||
### One-Line Install (Recommended)
|
||||
|
||||
### Get Latest Release Binaries
|
||||
**Unix/Linux/macOS:**
|
||||
|
||||
#### Windows
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/danielmiessler/fabric/main/scripts/installer/install.sh | bash
|
||||
```
|
||||
|
||||
`https://github.com/danielmiessler/fabric/releases/latest/download/fabric-windows-amd64.exe`
|
||||
**Windows PowerShell:**
|
||||
|
||||
#### macOS (arm64)
|
||||
```powershell
|
||||
iwr -useb https://raw.githubusercontent.com/danielmiessler/fabric/main/scripts/installer/install.ps1 | iex
|
||||
```
|
||||
|
||||
`curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-darwin-arm64 > fabric && chmod +x fabric && ./fabric --version`
|
||||
> See [scripts/installer/README.md](./scripts/installer/README.md) for custom installation options and troubleshooting.
|
||||
|
||||
#### macOS (amd64)
|
||||
### Manual Binary Downloads
|
||||
|
||||
`curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-darwin-amd64 > fabric && chmod +x fabric && ./fabric --version`
|
||||
|
||||
#### Linux (amd64)
|
||||
|
||||
`curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-linux-amd64 > fabric && chmod +x fabric && ./fabric --version`
|
||||
|
||||
#### Linux (arm64)
|
||||
|
||||
`curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-linux-arm64 > fabric && chmod +x fabric && ./fabric --version`
|
||||
The latest release binary archives and their expected SHA256 hashes can be found at <https://github.com/danielmiessler/fabric/releases/latest>
|
||||
|
||||
### Using package managers
|
||||
|
||||
@@ -191,6 +239,12 @@ alias fabric='fabric-ai'
|
||||
|
||||
`yay -S fabric-ai`
|
||||
|
||||
#### Windows
|
||||
|
||||
Use the official Microsoft supported `Winget` tool:
|
||||
|
||||
`winget install danielmiessler.Fabric`
|
||||
|
||||
### From Source
|
||||
|
||||
To install Fabric, [make sure Go is installed](https://go.dev/doc/install), and then run the following command.
|
||||
@@ -200,6 +254,35 @@ To install Fabric, [make sure Go is installed](https://go.dev/doc/install), and
|
||||
go install github.com/danielmiessler/fabric/cmd/fabric@latest
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
Run Fabric using pre-built Docker images:
|
||||
|
||||
```bash
|
||||
# Use latest image from Docker Hub
|
||||
docker run --rm -it kayvan/fabric:latest --version
|
||||
|
||||
# Use specific version from GHCR
|
||||
docker run --rm -it ghcr.io/ksylvan/fabric:v1.4.305 --version
|
||||
|
||||
# Run setup (first time)
|
||||
mkdir -p $HOME/.fabric-config
|
||||
docker run --rm -it -v $HOME/.fabric-config:/root/.config/fabric kayvan/fabric:latest --setup
|
||||
|
||||
# Use Fabric with your patterns
|
||||
docker run --rm -it -v $HOME/.fabric-config:/root/.config/fabric kayvan/fabric:latest -p summarize
|
||||
|
||||
# Run the REST API server
|
||||
docker run --rm -it -p 8080:8080 -v $HOME/.fabric-config:/root/.config/fabric kayvan/fabric:latest --serve
|
||||
```
|
||||
|
||||
**Images available at:**
|
||||
|
||||
- Docker Hub: [kayvan/fabric](https://hub.docker.com/repository/docker/kayvan/fabric/general)
|
||||
- GHCR: [ksylvan/fabric](https://github.com/ksylvan/fabric/pkgs/container/fabric)
|
||||
|
||||
See [scripts/docker/README.md](./scripts/docker/README.md) for building custom images and advanced configuration.
|
||||
|
||||
### Environment Variables
|
||||
|
||||
You may need to set some environment variables in your `~/.bashrc` on linux or `~/.zshrc` file on mac to be able to run the `fabric` command. Here is an example of what you can add:
|
||||
@@ -235,6 +318,13 @@ fabric --setup
|
||||
|
||||
If everything works you are good to go.
|
||||
|
||||
### Per-Pattern Model Mapping
|
||||
|
||||
You can configure specific models for individual patterns using environment variables
|
||||
like `FABRIC_MODEL_PATTERN_NAME=vendor|model`
|
||||
|
||||
This makes it easy to maintain these per-pattern model mappings in your shell startup files.
|
||||
|
||||
### Add aliases for all patterns
|
||||
|
||||
In order to add aliases for all your patterns and use them directly as commands ie. `summarize` instead of `fabric --pattern summarize`
|
||||
@@ -428,6 +518,25 @@ Fabric provides shell completion scripts for Zsh, Bash, and Fish
|
||||
shells, making it easier to use the CLI by providing tab completion
|
||||
for commands and options.
|
||||
|
||||
#### Quick install (no clone required)
|
||||
|
||||
You can install completions directly via a one-liner:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/danielmiessler/Fabric/refs/heads/main/completions/setup-completions.sh | sh
|
||||
```
|
||||
|
||||
Optional variants:
|
||||
|
||||
```bash
|
||||
# Dry-run (see actions without changing your system)
|
||||
curl -fsSL https://raw.githubusercontent.com/danielmiessler/Fabric/refs/heads/main/completions/setup-completions.sh | sh -s -- --dry-run
|
||||
|
||||
# Override the download source (advanced)
|
||||
FABRIC_COMPLETIONS_BASE_URL="https://raw.githubusercontent.com/danielmiessler/Fabric/refs/heads/main/completions" \
|
||||
sh -c "$(curl -fsSL https://raw.githubusercontent.com/danielmiessler/Fabric/refs/heads/main/completions/setup-completions.sh)"
|
||||
```
|
||||
|
||||
#### Zsh Completion
|
||||
|
||||
To enable Zsh completion:
|
||||
@@ -498,6 +607,7 @@ Application Options:
|
||||
-U, --updatepatterns Update patterns
|
||||
-c, --copy Copy to clipboard
|
||||
-m, --model= Choose model
|
||||
-V, --vendor= Specify vendor for chosen model (e.g., -V "LM Studio" -m openai/gpt-oss-20b)
|
||||
--modelContextLength= Model context length (only affects ollama)
|
||||
-o, --output= Output to file
|
||||
--output-session Output the entire session (also a temporary one) to the output file
|
||||
@@ -522,6 +632,7 @@ Application Options:
|
||||
--printsession= Print session
|
||||
--readability Convert HTML input into a clean, readable view
|
||||
--input-has-vars Apply variables to user input
|
||||
--no-variable-replacement Disable pattern variable replacement
|
||||
--dry-run Show what would be sent to the model without actually sending it
|
||||
--serve Serve the Fabric Rest API
|
||||
--serveOllama Serve the Fabric Rest API with ollama endpoints
|
||||
@@ -536,7 +647,7 @@ Application Options:
|
||||
--liststrategies List all strategies
|
||||
--listvendors List all vendors
|
||||
--shell-complete-list Output raw list without headers/formatting (for shell completion)
|
||||
--search Enable web search tool for supported models (Anthropic, OpenAI)
|
||||
--search Enable web search tool for supported models (Anthropic, OpenAI, Gemini)
|
||||
--search-location= Set location for web search results (e.g., 'America/Los_Angeles')
|
||||
--image-file= Save generated image to specified file path (e.g., 'output.png')
|
||||
--image-size= Image dimensions: 1024x1024, 1536x1024, 1024x1536, auto (default: auto)
|
||||
@@ -544,12 +655,33 @@ Application Options:
|
||||
--image-compression= Compression level 0-100 for JPEG/WebP formats (default: not set)
|
||||
--image-background= Background type: opaque, transparent (default: opaque, only for
|
||||
PNG/WebP)
|
||||
|
||||
--suppress-think Suppress text enclosed in thinking tags
|
||||
--think-start-tag= Start tag for thinking sections (default: <think>)
|
||||
--think-end-tag= End tag for thinking sections (default: </think>)
|
||||
--disable-responses-api Disable OpenAI Responses API (default: false)
|
||||
--voice= TTS voice name for supported models (e.g., Kore, Charon, Puck)
|
||||
(default: Kore)
|
||||
--list-gemini-voices List all available Gemini TTS voices
|
||||
--notification Send desktop notification when command completes
|
||||
--notification-command= Custom command to run for notifications (overrides built-in
|
||||
notifications)
|
||||
--yt-dlp-args= Additional arguments to pass to yt-dlp (e.g. '--cookies-from-browser brave')
|
||||
--thinking= Set reasoning/thinking level (e.g., off, low, medium, high, or
|
||||
numeric tokens for Anthropic or Google Gemini)
|
||||
--debug= Set debug level (0: off, 1: basic, 2: detailed, 3: trace)
|
||||
Help Options:
|
||||
-h, --help Show this help message
|
||||
|
||||
```
|
||||
|
||||
### Debug Levels
|
||||
|
||||
Use the `--debug` flag to control runtime logging:
|
||||
|
||||
- `0`: off (default)
|
||||
- `1`: basic debug info
|
||||
- `2`: detailed debugging
|
||||
- `3`: trace level
|
||||
|
||||
## Our approach to prompting
|
||||
|
||||
Fabric _Patterns_ are different than most prompts you'll see.
|
||||
@@ -559,7 +691,7 @@ Fabric _Patterns_ are different than most prompts you'll see.
|
||||
Here's an example of a Fabric Pattern.
|
||||
|
||||
```bash
|
||||
https://github.com/danielmiessler/fabric/blob/main/patterns/extract_wisdom/system.md
|
||||
https://github.com/danielmiessler/Fabric/blob/main/data/patterns/extract_wisdom/system.md
|
||||
```
|
||||
|
||||
<img width="1461" alt="pattern-example" src="https://github.com/danielmiessler/fabric/assets/50654/b910c551-9263-405f-9735-71ca69bbab6d">
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v1.4.256"
|
||||
var version = "v1.4.307"
|
||||
|
||||
@@ -101,6 +101,7 @@ generate_changelog --cache /path/to/cache.db
|
||||
| `--force-pr-sync` | | Force a full PR sync from GitHub | false |
|
||||
| `--token` | | GitHub API token | `$GITHUB_TOKEN` |
|
||||
| `--ai-summarize` | | Generate AI-enhanced summaries using Fabric | false |
|
||||
| `--release` | | Update GitHub release description with AI summary for version | |
|
||||
|
||||
## Output Format
|
||||
|
||||
|
||||
Binary file not shown.
30
cmd/generate_changelog/internal/cache/cache.go
vendored
30
cmd/generate_changelog/internal/cache/cache.go
vendored
@@ -4,6 +4,7 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/danielmiessler/fabric/cmd/generate_changelog/internal/git"
|
||||
@@ -201,7 +202,14 @@ func (c *Cache) GetVersions() (map[string]*git.Version, error) {
|
||||
}
|
||||
|
||||
if dateStr.Valid {
|
||||
v.Date, _ = time.Parse(time.RFC3339, dateStr.String)
|
||||
// Try RFC3339Nano first (for nanosecond precision), then fall back to RFC3339
|
||||
v.Date, err = time.Parse(time.RFC3339Nano, dateStr.String)
|
||||
if err != nil {
|
||||
v.Date, err = time.Parse(time.RFC3339, dateStr.String)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error parsing date '%s' for version '%s': %v. Expected format: RFC3339 or RFC3339Nano.\n", dateStr.String, v.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if prNumbersJSON != "" {
|
||||
@@ -260,6 +268,26 @@ func (c *Cache) Clear() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// VersionExists checks if a version already exists in the cache
|
||||
func (c *Cache) VersionExists(version string) (bool, error) {
|
||||
var count int
|
||||
err := c.db.QueryRow("SELECT COUNT(*) FROM versions WHERE name = ?", version).Scan(&count)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// CommitExists checks if a commit already exists in the cache
|
||||
func (c *Cache) CommitExists(hash string) (bool, error) {
|
||||
var count int
|
||||
err := c.db.QueryRow("SELECT COUNT(*) FROM commits WHERE sha = ?", hash).Scan(&count)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// GetLastPRSync returns the timestamp of the last PR sync
|
||||
func (c *Cache) GetLastPRSync() (time.Time, error) {
|
||||
var timestamp string
|
||||
|
||||
@@ -65,7 +65,7 @@ func (g *Generator) Generate() (string, error) {
|
||||
return "", fmt.Errorf("failed to collect data: %w", err)
|
||||
}
|
||||
|
||||
if err := g.fetchPRs(); err != nil {
|
||||
if err := g.fetchPRs(g.cfg.ForcePRSync); err != nil {
|
||||
return "", fmt.Errorf("failed to fetch PRs: %w", err)
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ func (g *Generator) collectData() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Generator) fetchPRs() error {
|
||||
func (g *Generator) fetchPRs(forcePRSync bool) error {
|
||||
// First, load all cached PRs
|
||||
if g.cache != nil {
|
||||
cachedPRs, err := g.cache.GetAllPRs()
|
||||
@@ -229,7 +229,7 @@ func (g *Generator) fetchPRs() error {
|
||||
}
|
||||
// If we have never synced or it's been more than 24 hours, do a full sync
|
||||
// Also sync if we have versions with PR numbers that aren't cached
|
||||
needsSync := lastSync.IsZero() || time.Since(lastSync) > 24*time.Hour || g.cfg.ForcePRSync || missingPRs
|
||||
needsSync := lastSync.IsZero() || time.Since(lastSync) > 24*time.Hour || forcePRSync || missingPRs
|
||||
|
||||
if !needsSync {
|
||||
fmt.Fprintf(os.Stderr, "Using cached PR data (last sync: %s)\n", lastSync.Format("2006-01-02 15:04:05"))
|
||||
@@ -697,3 +697,109 @@ func hashContent(content string) string {
|
||||
hash := sha256.Sum256([]byte(content))
|
||||
return fmt.Sprintf("%x", hash)
|
||||
}
|
||||
|
||||
// SyncDatabase performs a comprehensive database synchronization and validation
|
||||
func (g *Generator) SyncDatabase() error {
|
||||
if g.cache == nil {
|
||||
return fmt.Errorf("cache is disabled, cannot sync database")
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "[SYNC] Starting database synchronization...\n")
|
||||
|
||||
// Step 1: Force PR sync (pass true explicitly)
|
||||
fmt.Fprintf(os.Stderr, "[PR_SYNC] Forcing PR sync from GitHub...\n")
|
||||
if err := g.fetchPRs(true); err != nil {
|
||||
return fmt.Errorf("failed to sync PRs: %w", err)
|
||||
}
|
||||
|
||||
// Step 2: Rebuild git history and verify versions/commits completeness
|
||||
fmt.Fprintf(os.Stderr, "[VERIFY] Verifying git history and version completeness...\n")
|
||||
if err := g.syncGitHistory(); err != nil {
|
||||
return fmt.Errorf("failed to sync git history: %w", err)
|
||||
}
|
||||
|
||||
// Step 3: Verify commit-PR mappings
|
||||
fmt.Fprintf(os.Stderr, "[MAPPING] Verifying commit-PR mappings...\n")
|
||||
if err := g.verifyCommitPRMappings(); err != nil {
|
||||
return fmt.Errorf("failed to verify commit-PR mappings: %w", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "[SUCCESS] Database synchronization completed successfully!\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
// syncGitHistory walks the complete git history and ensures all versions and commits are cached
|
||||
func (g *Generator) syncGitHistory() error {
|
||||
// Walk complete git history (reuse existing logic)
|
||||
versions, err := g.gitWalker.WalkHistory()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to walk git history: %w", err)
|
||||
}
|
||||
|
||||
// Save only new versions and commits (preserve existing data)
|
||||
var newVersions, newCommits int
|
||||
for _, version := range versions {
|
||||
// Only save version if it doesn't exist
|
||||
exists, err := g.cache.VersionExists(version.Name)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to check existence of version %s: %v. This may affect the completeness of the sync operation.\n", version.Name, err)
|
||||
continue
|
||||
}
|
||||
if !exists {
|
||||
if err := g.cache.SaveVersion(version); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to save version %s: %v\n", version.Name, err)
|
||||
} else {
|
||||
newVersions++
|
||||
}
|
||||
}
|
||||
|
||||
// Only save commits that don't exist
|
||||
for _, commit := range version.Commits {
|
||||
exists, err := g.cache.CommitExists(commit.SHA)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to check commit %s existence: %v\n", commit.SHA, err)
|
||||
continue
|
||||
}
|
||||
if !exists {
|
||||
if err := g.cache.SaveCommit(commit, version.Name); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to save commit %s: %v\n", commit.SHA, err)
|
||||
} else {
|
||||
newCommits++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update last processed tag
|
||||
if latestTag, err := g.gitWalker.GetLatestTag(); err == nil && latestTag != "" {
|
||||
if err := g.cache.SetLastProcessedTag(latestTag); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to update last processed tag: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, " Added %d new versions and %d new commits (preserved existing data)\n", newVersions, newCommits)
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyCommitPRMappings ensures all PR commits have proper mappings
|
||||
func (g *Generator) verifyCommitPRMappings() error {
|
||||
// Get all cached PRs
|
||||
allPRs, err := g.cache.GetAllPRs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get cached PRs: %w", err)
|
||||
}
|
||||
|
||||
// Convert to slice for batch operations (reuse existing logic)
|
||||
var prSlice []*github.PR
|
||||
for _, pr := range allPRs {
|
||||
prSlice = append(prSlice, pr)
|
||||
}
|
||||
|
||||
// Save commit-PR mappings (reuse existing logic)
|
||||
if err := g.cache.SaveCommitPRMappings(prSlice); err != nil {
|
||||
return fmt.Errorf("failed to save commit-PR mappings: %w", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, " Verified mappings for %d PRs\n", len(prSlice))
|
||||
return nil
|
||||
}
|
||||
|
||||
115
cmd/generate_changelog/internal/changelog/generator_test.go
Normal file
115
cmd/generate_changelog/internal/changelog/generator_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package changelog
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/danielmiessler/fabric/cmd/generate_changelog/internal/config"
|
||||
)
|
||||
|
||||
func TestDetectVersionFromNix(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
t.Run("version.nix exists", func(t *testing.T) {
|
||||
versionNixContent := `"1.2.3"`
|
||||
versionNixPath := filepath.Join(tempDir, "version.nix")
|
||||
err := os.WriteFile(versionNixPath, []byte(versionNixContent), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to write version.nix: %v", err)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(versionNixPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read version.nix: %v", err)
|
||||
}
|
||||
|
||||
versionRegex := regexp.MustCompile(`"([^"]+)"`)
|
||||
matches := versionRegex.FindStringSubmatch(string(data))
|
||||
|
||||
if len(matches) <= 1 {
|
||||
t.Fatalf("No version found in version.nix")
|
||||
}
|
||||
|
||||
version := matches[1]
|
||||
if version != "1.2.3" {
|
||||
t.Errorf("Expected version 1.2.3, got %s", version)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnsureIncomingDir(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
incomingDir := filepath.Join(tempDir, "incoming")
|
||||
|
||||
cfg := &config.Config{
|
||||
IncomingDir: incomingDir,
|
||||
}
|
||||
|
||||
g := &Generator{cfg: cfg}
|
||||
|
||||
err := g.ensureIncomingDir()
|
||||
if err != nil {
|
||||
t.Fatalf("ensureIncomingDir failed: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(incomingDir); os.IsNotExist(err) {
|
||||
t.Errorf("Incoming directory was not created")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertVersionAtTop(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
changelogPath := filepath.Join(tempDir, "CHANGELOG.md")
|
||||
|
||||
cfg := &config.Config{
|
||||
RepoPath: tempDir,
|
||||
}
|
||||
|
||||
g := &Generator{cfg: cfg}
|
||||
|
||||
t.Run("new changelog", func(t *testing.T) {
|
||||
entry := "## v1.0.0 (2025-01-01)\n\n- Initial release"
|
||||
|
||||
err := g.insertVersionAtTop(entry)
|
||||
if err != nil {
|
||||
t.Fatalf("insertVersionAtTop failed: %v", err)
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(changelogPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read changelog: %v", err)
|
||||
}
|
||||
|
||||
expected := "# Changelog\n\n## v1.0.0 (2025-01-01)\n\n- Initial release\n"
|
||||
if string(content) != expected {
|
||||
t.Errorf("Expected:\n%s\nGot:\n%s", expected, string(content))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("existing changelog", func(t *testing.T) {
|
||||
existingContent := "# Changelog\n\n## v0.9.0 (2024-12-01)\n\n- Previous release"
|
||||
err := os.WriteFile(changelogPath, []byte(existingContent), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to write existing changelog: %v", err)
|
||||
}
|
||||
|
||||
entry := "## v1.0.0 (2025-01-01)\n\n- New release"
|
||||
|
||||
err = g.insertVersionAtTop(entry)
|
||||
if err != nil {
|
||||
t.Fatalf("insertVersionAtTop failed: %v", err)
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(changelogPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read changelog: %v", err)
|
||||
}
|
||||
|
||||
expected := "# Changelog\n\n## v1.0.0 (2025-01-01)\n\n- New release\n## v0.9.0 (2024-12-01)\n\n- Previous release"
|
||||
if string(content) != expected {
|
||||
t.Errorf("Expected:\n%s\nGot:\n%s", expected, string(content))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package changelog
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/danielmiessler/fabric/cmd/generate_changelog/internal/github"
|
||||
)
|
||||
|
||||
func TestIsMergeCommit(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
commit github.PRCommit
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "Regular commit with single parent",
|
||||
commit: github.PRCommit{
|
||||
SHA: "abc123",
|
||||
Message: "Fix bug in user authentication",
|
||||
Author: "John Doe",
|
||||
Date: time.Now(),
|
||||
Parents: []string{"def456"},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Merge commit with multiple parents",
|
||||
commit: github.PRCommit{
|
||||
SHA: "abc123",
|
||||
Message: "Merge pull request #42 from feature/auth",
|
||||
Author: "GitHub",
|
||||
Date: time.Now(),
|
||||
Parents: []string{"def456", "ghi789"},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Merge commit detected by message pattern only",
|
||||
commit: github.PRCommit{
|
||||
SHA: "abc123",
|
||||
Message: "Merge pull request #123 from user/feature-branch",
|
||||
Author: "GitHub",
|
||||
Date: time.Now(),
|
||||
Parents: []string{}, // Empty parents - fallback to message detection
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Merge branch commit pattern",
|
||||
commit: github.PRCommit{
|
||||
SHA: "abc123",
|
||||
Message: "Merge branch 'feature' into main",
|
||||
Author: "Developer",
|
||||
Date: time.Now(),
|
||||
Parents: []string{"def456"}, // Single parent but merge pattern
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Regular commit with no merge patterns",
|
||||
commit: github.PRCommit{
|
||||
SHA: "abc123",
|
||||
Message: "Add new feature for user management",
|
||||
Author: "Jane Doe",
|
||||
Date: time.Now(),
|
||||
Parents: []string{"def456"},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := isMergeCommit(tt.commit)
|
||||
if result != tt.expected {
|
||||
t.Errorf("isMergeCommit() = %v, expected %v for commit: %s",
|
||||
result, tt.expected, tt.commit.Message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
521
cmd/generate_changelog/internal/changelog/processing.go
Normal file
521
cmd/generate_changelog/internal/changelog/processing.go
Normal file
@@ -0,0 +1,521 @@
|
||||
package changelog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/danielmiessler/fabric/cmd/generate_changelog/internal/git"
|
||||
"github.com/danielmiessler/fabric/cmd/generate_changelog/internal/github"
|
||||
)
|
||||
|
||||
var (
|
||||
mergePatterns []*regexp.Regexp
|
||||
mergePatternsOnce sync.Once
|
||||
)
|
||||
|
||||
// getMergePatterns returns the compiled merge patterns, initializing them lazily
|
||||
func getMergePatterns() []*regexp.Regexp {
|
||||
mergePatternsOnce.Do(func() {
|
||||
mergePatterns = []*regexp.Regexp{
|
||||
regexp.MustCompile(`^Merge pull request #\d+`), // "Merge pull request #123 from..."
|
||||
regexp.MustCompile(`^Merge branch '.*' into .*`), // "Merge branch 'feature' into main"
|
||||
regexp.MustCompile(`^Merge remote-tracking branch`), // "Merge remote-tracking branch..."
|
||||
regexp.MustCompile(`^Merge '.*' into .*`), // "Merge 'feature' into main"
|
||||
}
|
||||
})
|
||||
return mergePatterns
|
||||
}
|
||||
|
||||
// isMergeCommit determines if a commit is a merge commit based on its parents and message patterns.
|
||||
func isMergeCommit(commit github.PRCommit) bool {
|
||||
// Primary method: Check parent count (merge commits have multiple parents)
|
||||
if len(commit.Parents) > 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Fallback method: Check commit message patterns
|
||||
mergePatterns := getMergePatterns()
|
||||
for _, pattern := range mergePatterns {
|
||||
if pattern.MatchString(commit.Message) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// calculateVersionDate determines the version date based on the most recent commit date from the provided PRs.
|
||||
//
|
||||
// If no valid commit dates are found, the function falls back to the current time.
|
||||
// The function iterates through the provided PRs and their associated commits, comparing commit dates
|
||||
// to identify the most recent one. If a valid date is found, it is returned; otherwise, the fallback is used.
|
||||
func calculateVersionDate(fetchedPRs []*github.PR) time.Time {
|
||||
versionDate := time.Now() // fallback to current time
|
||||
if len(fetchedPRs) > 0 {
|
||||
var mostRecentCommitDate time.Time
|
||||
for _, pr := range fetchedPRs {
|
||||
for _, commit := range pr.Commits {
|
||||
if commit.Date.After(mostRecentCommitDate) {
|
||||
mostRecentCommitDate = commit.Date
|
||||
}
|
||||
}
|
||||
}
|
||||
if !mostRecentCommitDate.IsZero() {
|
||||
versionDate = mostRecentCommitDate
|
||||
}
|
||||
}
|
||||
return versionDate
|
||||
}
|
||||
|
||||
// ProcessIncomingPR processes a single PR for changelog entry creation
|
||||
func (g *Generator) ProcessIncomingPR(prNumber int) error {
|
||||
if err := g.validatePRState(prNumber); err != nil {
|
||||
return fmt.Errorf("PR validation failed: %w", err)
|
||||
}
|
||||
|
||||
if err := g.validateGitStatus(); err != nil {
|
||||
return fmt.Errorf("git status validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Now fetch the full PR with commits for content generation
|
||||
pr, err := g.ghClient.GetPRWithCommits(prNumber)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch PR %d: %w", prNumber, err)
|
||||
}
|
||||
|
||||
content := g.formatPR(pr)
|
||||
|
||||
if g.cfg.EnableAISummary {
|
||||
aiContent, err := SummarizeVersionContent(content)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: AI summarization failed: %v\n", err)
|
||||
} else if !checkForAIError(aiContent) {
|
||||
content = strings.TrimSpace(aiContent)
|
||||
}
|
||||
}
|
||||
|
||||
if err := g.ensureIncomingDir(); err != nil {
|
||||
return fmt.Errorf("failed to create incoming directory: %w", err)
|
||||
}
|
||||
|
||||
filename := filepath.Join(g.cfg.IncomingDir, fmt.Sprintf("%d.txt", prNumber))
|
||||
|
||||
// Ensure content ends with a single newline
|
||||
content = strings.TrimSpace(content) + "\n"
|
||||
|
||||
if err := os.WriteFile(filename, []byte(content), 0644); err != nil {
|
||||
return fmt.Errorf("failed to write incoming file: %w", err)
|
||||
}
|
||||
|
||||
if err := g.commitAndPushIncoming(prNumber, filename); err != nil {
|
||||
return fmt.Errorf("failed to commit and push: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully created incoming changelog entry: %s\n", filename)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateNewChangelogEntry aggregates all incoming PR files for release and includes direct commits
|
||||
func (g *Generator) CreateNewChangelogEntry(version string) error {
|
||||
files, err := filepath.Glob(filepath.Join(g.cfg.IncomingDir, "*.txt"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to scan incoming directory: %w", err)
|
||||
}
|
||||
|
||||
var content strings.Builder
|
||||
var processingErrors []string
|
||||
|
||||
// First, aggregate all incoming PR files
|
||||
for _, file := range files {
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
processingErrors = append(processingErrors, fmt.Sprintf("failed to read %s: %v", file, err))
|
||||
continue // Continue to attempt processing other files
|
||||
}
|
||||
content.WriteString(string(data))
|
||||
// Note: No extra newline needed here as each incoming file already ends with a newline
|
||||
}
|
||||
|
||||
if len(processingErrors) > 0 {
|
||||
return fmt.Errorf("encountered errors while processing incoming files: %s", strings.Join(processingErrors, "; "))
|
||||
}
|
||||
|
||||
// Extract PR numbers and their commit SHAs from processed files to avoid including their commits as "direct"
|
||||
processedPRs := make(map[int]bool)
|
||||
processedCommitSHAs := make(map[string]bool)
|
||||
var fetchedPRs []*github.PR
|
||||
var prNumbers []int
|
||||
|
||||
for _, file := range files {
|
||||
// Extract PR number from filename (e.g., "1640.txt" -> 1640)
|
||||
filename := filepath.Base(file)
|
||||
if prNumStr := strings.TrimSuffix(filename, ".txt"); prNumStr != filename {
|
||||
if prNum, err := strconv.Atoi(prNumStr); err == nil {
|
||||
processedPRs[prNum] = true
|
||||
prNumbers = append(prNumbers, prNum)
|
||||
|
||||
// Fetch the PR to get its commit SHAs
|
||||
if pr, err := g.ghClient.GetPRWithCommits(prNum); err == nil {
|
||||
fetchedPRs = append(fetchedPRs, pr)
|
||||
for _, commit := range pr.Commits {
|
||||
processedCommitSHAs[commit.SHA] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now add direct commits since the last release, excluding commits from processed PRs
|
||||
directCommitsContent, err := g.getDirectCommitsSinceLastRelease(processedPRs, processedCommitSHAs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get direct commits since last release: %w", err)
|
||||
}
|
||||
content.WriteString(directCommitsContent)
|
||||
|
||||
// Check if we have any content at all
|
||||
if content.Len() == 0 {
|
||||
if len(files) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "No incoming PR files found in %s and no direct commits since last release\n", g.cfg.IncomingDir)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "No content found in incoming files and no direct commits since last release\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Calculate the version date for the changelog entry as the most recent commit date from processed PRs
|
||||
versionDate := calculateVersionDate(fetchedPRs)
|
||||
|
||||
entry := fmt.Sprintf("## %s (%s)\n\n%s",
|
||||
version, versionDate.Format("2006-01-02"), strings.TrimLeft(content.String(), "\n"))
|
||||
|
||||
if err := g.insertVersionAtTop(entry); err != nil {
|
||||
return fmt.Errorf("failed to update CHANGELOG.md: %w", err)
|
||||
}
|
||||
|
||||
if g.cache != nil {
|
||||
// Cache the fetched PRs using the same logic as normal changelog generation
|
||||
if len(fetchedPRs) > 0 {
|
||||
// Save PRs to cache
|
||||
if err := g.cache.SavePRBatch(fetchedPRs); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to save PR batch to cache: %v\n", err)
|
||||
}
|
||||
|
||||
// Save SHA→PR mappings for lightning-fast git operations
|
||||
if err := g.cache.SaveCommitPRMappings(fetchedPRs); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to cache commit mappings: %v\n", err)
|
||||
}
|
||||
|
||||
// Save individual commits to cache for each PR
|
||||
for _, pr := range fetchedPRs {
|
||||
for _, commit := range pr.Commits {
|
||||
// Use actual commit timestamp, with fallback to current time if invalid
|
||||
commitDate := commit.Date
|
||||
if commitDate.IsZero() {
|
||||
commitDate = time.Now()
|
||||
fmt.Fprintf(os.Stderr, "Warning: Commit %s has invalid timestamp, using current time as fallback\n", commit.SHA)
|
||||
}
|
||||
|
||||
// Convert github.PRCommit to git.Commit
|
||||
gitCommit := &git.Commit{
|
||||
SHA: commit.SHA,
|
||||
Message: commit.Message,
|
||||
Author: commit.Author,
|
||||
Email: commit.Email, // Use email from GitHub API
|
||||
Date: commitDate, // Use actual commit timestamp from GitHub API
|
||||
IsMerge: isMergeCommit(commit), // Detect merge commits using parents and message patterns
|
||||
PRNumber: pr.Number,
|
||||
}
|
||||
if err := g.cache.SaveCommit(gitCommit, version); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to save commit %s to cache: %v\n", commit.SHA, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a proper new version entry for the database
|
||||
newVersionEntry := &git.Version{
|
||||
Name: version,
|
||||
Date: versionDate, // Use most recent commit date instead of current time
|
||||
CommitSHA: "", // Will be set when the release commit is made
|
||||
PRNumbers: prNumbers, // Now we have the actual PR numbers
|
||||
AISummary: content.String(),
|
||||
}
|
||||
|
||||
if err := g.cache.SaveVersion(newVersionEntry); err != nil {
|
||||
return fmt.Errorf("failed to save new version entry to database: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
// Convert to relative path for git operations
|
||||
relativeFile, err := filepath.Rel(g.cfg.RepoPath, file)
|
||||
if err != nil {
|
||||
relativeFile = file
|
||||
}
|
||||
|
||||
// Use git remove to handle both filesystem and git index
|
||||
if err := g.gitWalker.RemoveFile(relativeFile); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Failed to remove %s from git index: %v\n", relativeFile, err)
|
||||
// Fallback to filesystem-only removal
|
||||
if err := os.Remove(file); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: Failed to remove %s from the filesystem after failing to remove it from the git index.\n", relativeFile)
|
||||
fmt.Fprintf(os.Stderr, "Filesystem error: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "Manual intervention required:\n")
|
||||
fmt.Fprintf(os.Stderr, " 1. Remove the file %s manually (using the OS-specific command)\n", file)
|
||||
fmt.Fprintf(os.Stderr, " 2. Remove from git index: git rm --cached %s\n", relativeFile)
|
||||
fmt.Fprintf(os.Stderr, " 3. Or reset git index: git reset HEAD %s\n", relativeFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := g.stageChangesForRelease(); err != nil {
|
||||
return fmt.Errorf("critical: failed to stage changes for release: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully processed %d incoming PR files for version %s\n", len(files), version)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getDirectCommitsSinceLastRelease gets all direct commits (not part of PRs) since the last release
|
||||
func (g *Generator) getDirectCommitsSinceLastRelease(processedPRs map[int]bool, processedCommitSHAs map[string]bool) (string, error) {
|
||||
// Get the latest tag to determine what commits are unreleased
|
||||
latestTag, err := g.gitWalker.GetLatestTag()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get latest tag: %w", err)
|
||||
}
|
||||
|
||||
// Get all commits since the latest tag
|
||||
unreleasedVersion, err := g.gitWalker.WalkCommitsSinceTag(latestTag)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to walk commits since tag %s: %w", latestTag, err)
|
||||
}
|
||||
|
||||
if unreleasedVersion == nil || len(unreleasedVersion.Commits) == 0 {
|
||||
return "", nil // No unreleased commits
|
||||
}
|
||||
|
||||
// Filter out commits that are part of PRs (we already have those from incoming files)
|
||||
// and format the direct commits
|
||||
var directCommits []*git.Commit
|
||||
for _, commit := range unreleasedVersion.Commits {
|
||||
// Skip version bump commits
|
||||
if commit.IsVersion {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip commits that belong to PRs we've already processed from incoming files (by PR number)
|
||||
if commit.PRNumber > 0 && processedPRs[commit.PRNumber] {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip commits whose SHA is already included in processed PRs (this catches commits
|
||||
// that might not have been detected as part of a PR but are actually in the PR)
|
||||
if processedCommitSHAs[commit.SHA] {
|
||||
continue
|
||||
}
|
||||
|
||||
// Only include commits that are NOT part of any PR (direct commits)
|
||||
if commit.PRNumber == 0 {
|
||||
directCommits = append(directCommits, commit)
|
||||
}
|
||||
}
|
||||
|
||||
if len(directCommits) == 0 {
|
||||
return "", nil // No direct commits
|
||||
}
|
||||
|
||||
// Format the direct commits similar to how it's done in generateRawVersionContent
|
||||
var sb strings.Builder
|
||||
sb.WriteString("### Direct commits\n\n")
|
||||
|
||||
// Sort direct commits by date (newest first) for consistent ordering
|
||||
sort.Slice(directCommits, func(i, j int) bool {
|
||||
return directCommits[i].Date.After(directCommits[j].Date)
|
||||
})
|
||||
|
||||
for _, commit := range directCommits {
|
||||
message := g.formatCommitMessage(strings.TrimSpace(commit.Message))
|
||||
if message != "" && !g.isDuplicateMessage(message, directCommits) {
|
||||
sb.WriteString(fmt.Sprintf("- %s\n", message))
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
// validatePRState validates that a PR is in the correct state for processing
|
||||
func (g *Generator) validatePRState(prNumber int) error {
|
||||
// Use lightweight validation call that doesn't fetch commits
|
||||
details, err := g.ghClient.GetPRValidationDetails(prNumber)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch PR %d: %w", prNumber, err)
|
||||
}
|
||||
|
||||
if details.State != "open" {
|
||||
return fmt.Errorf("PR %d is not open (current state: %s)", prNumber, details.State)
|
||||
}
|
||||
|
||||
if !details.Mergeable {
|
||||
return fmt.Errorf("PR %d is not mergeable - please resolve conflicts first", prNumber)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateGitStatus ensures the working directory is clean
|
||||
func (g *Generator) validateGitStatus() error {
|
||||
isClean, err := g.gitWalker.IsWorkingDirectoryClean()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check git status: %w", err)
|
||||
}
|
||||
|
||||
if !isClean {
|
||||
// Get detailed status for better error message
|
||||
statusDetails, statusErr := g.gitWalker.GetStatusDetails()
|
||||
if statusErr == nil && statusDetails != "" {
|
||||
return fmt.Errorf("working directory is not clean - please commit or stash changes before proceeding:\n%s", statusDetails)
|
||||
}
|
||||
return fmt.Errorf("working directory is not clean - please commit or stash changes before proceeding")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureIncomingDir creates the incoming directory if it doesn't exist
|
||||
func (g *Generator) ensureIncomingDir() error {
|
||||
if err := os.MkdirAll(g.cfg.IncomingDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create directory %s: %w", g.cfg.IncomingDir, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// commitAndPushIncoming commits and optionally pushes the incoming changelog file
|
||||
func (g *Generator) commitAndPushIncoming(prNumber int, filename string) error {
|
||||
relativeFilename, err := filepath.Rel(g.cfg.RepoPath, filename)
|
||||
if err != nil {
|
||||
relativeFilename = filename
|
||||
}
|
||||
|
||||
// Add file to git index
|
||||
if err := g.gitWalker.AddFile(relativeFilename); err != nil {
|
||||
return fmt.Errorf("failed to add file %s: %w", relativeFilename, err)
|
||||
}
|
||||
|
||||
// Commit changes
|
||||
commitMessage := fmt.Sprintf("chore: incoming %d changelog entry", prNumber)
|
||||
_, err = g.gitWalker.CommitChanges(commitMessage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to commit changes: %w", err)
|
||||
}
|
||||
|
||||
// Push to remote if enabled
|
||||
if g.cfg.Push {
|
||||
if err := g.gitWalker.PushToRemote(); err != nil {
|
||||
return fmt.Errorf("failed to push to remote: %w", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Commit created successfully. Please review and push manually.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// detectVersion detects the current version from version.nix or git tags
|
||||
func (g *Generator) detectVersion() (string, error) {
|
||||
versionNixPath := filepath.Join(g.cfg.RepoPath, "version.nix")
|
||||
if _, err := os.Stat(versionNixPath); err == nil {
|
||||
data, err := os.ReadFile(versionNixPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read version.nix: %w", err)
|
||||
}
|
||||
|
||||
versionRegex := regexp.MustCompile(`"([^"]+)"`)
|
||||
matches := versionRegex.FindStringSubmatch(string(data))
|
||||
if len(matches) > 1 {
|
||||
return matches[1], nil
|
||||
}
|
||||
}
|
||||
|
||||
latestTag, err := g.gitWalker.GetLatestTag()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get latest tag: %w", err)
|
||||
}
|
||||
|
||||
if latestTag == "" {
|
||||
return "v1.0.0", nil
|
||||
}
|
||||
|
||||
return latestTag, nil
|
||||
}
|
||||
|
||||
// insertVersionAtTop inserts a new version entry at the top of CHANGELOG.md
|
||||
func (g *Generator) insertVersionAtTop(entry string) error {
|
||||
changelogPath := filepath.Join(g.cfg.RepoPath, "CHANGELOG.md")
|
||||
header := "# Changelog"
|
||||
headerRegex := regexp.MustCompile(`(?m)^# Changelog\s*`)
|
||||
|
||||
existingContent, err := os.ReadFile(changelogPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to read existing CHANGELOG.md: %w", err)
|
||||
}
|
||||
// File doesn't exist, create it.
|
||||
newContent := fmt.Sprintf("%s\n\n%s\n", header, entry)
|
||||
return os.WriteFile(changelogPath, []byte(newContent), 0644)
|
||||
}
|
||||
|
||||
contentStr := string(existingContent)
|
||||
var newContent string
|
||||
|
||||
if loc := headerRegex.FindStringIndex(contentStr); loc != nil {
|
||||
// Found the header, insert after it.
|
||||
insertionPoint := loc[1]
|
||||
// Skip any existing newlines after the header to avoid double spacing
|
||||
for insertionPoint < len(contentStr) && (contentStr[insertionPoint] == '\n' || contentStr[insertionPoint] == '\r') {
|
||||
insertionPoint++
|
||||
}
|
||||
// Insert with proper spacing: single newline after header, then entry, then newline before existing content
|
||||
newContent = contentStr[:loc[1]] + entry + "\n" + contentStr[insertionPoint:]
|
||||
} else {
|
||||
// Header not found, prepend everything.
|
||||
newContent = fmt.Sprintf("%s\n\n%s\n\n%s", header, entry, contentStr)
|
||||
}
|
||||
|
||||
return os.WriteFile(changelogPath, []byte(newContent), 0644)
|
||||
}
|
||||
|
||||
// stageChangesForRelease stages the modified files for the release commit
|
||||
func (g *Generator) stageChangesForRelease() error {
|
||||
changelogPath := filepath.Join(g.cfg.RepoPath, "CHANGELOG.md")
|
||||
relativeChangelog, err := filepath.Rel(g.cfg.RepoPath, changelogPath)
|
||||
if err != nil {
|
||||
relativeChangelog = "CHANGELOG.md"
|
||||
}
|
||||
|
||||
relativeCacheFile, err := filepath.Rel(g.cfg.RepoPath, g.cfg.CacheFile)
|
||||
if err != nil {
|
||||
relativeCacheFile = g.cfg.CacheFile
|
||||
}
|
||||
|
||||
// Add CHANGELOG.md to git index
|
||||
if err := g.gitWalker.AddFile(relativeChangelog); err != nil {
|
||||
return fmt.Errorf("failed to add %s: %w", relativeChangelog, err)
|
||||
}
|
||||
|
||||
// Add cache file to git index
|
||||
if err := g.gitWalker.AddFile(relativeCacheFile); err != nil {
|
||||
return fmt.Errorf("failed to add %s: %w", relativeCacheFile, err)
|
||||
}
|
||||
|
||||
// Note: Individual incoming files are now removed during the main processing loop
|
||||
// No need to remove the entire directory here
|
||||
|
||||
return nil
|
||||
}
|
||||
262
cmd/generate_changelog/internal/changelog/processing_test.go
Normal file
262
cmd/generate_changelog/internal/changelog/processing_test.go
Normal file
@@ -0,0 +1,262 @@
|
||||
package changelog
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/danielmiessler/fabric/cmd/generate_changelog/internal/config"
|
||||
)
|
||||
|
||||
func TestDetectVersion(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
versionNixContent string
|
||||
expectedVersion string
|
||||
shouldError bool
|
||||
}{
|
||||
{
|
||||
name: "valid version.nix",
|
||||
versionNixContent: `"1.2.3"`,
|
||||
expectedVersion: "1.2.3",
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "version with extra whitespace",
|
||||
versionNixContent: `"1.2.3" `,
|
||||
expectedVersion: "1.2.3",
|
||||
shouldError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create version.nix file
|
||||
versionNixPath := filepath.Join(tempDir, "version.nix")
|
||||
if err := os.WriteFile(versionNixPath, []byte(tt.versionNixContent), 0644); err != nil {
|
||||
t.Fatalf("Failed to create version.nix: %v", err)
|
||||
}
|
||||
|
||||
cfg := &config.Config{
|
||||
RepoPath: tempDir,
|
||||
}
|
||||
|
||||
g := &Generator{cfg: cfg}
|
||||
|
||||
version, err := g.detectVersion()
|
||||
if tt.shouldError && err == nil {
|
||||
t.Errorf("Expected error but got none")
|
||||
}
|
||||
if !tt.shouldError && err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if version != tt.expectedVersion {
|
||||
t.Errorf("Expected version '%s', got '%s'", tt.expectedVersion, version)
|
||||
}
|
||||
|
||||
// Clean up
|
||||
os.Remove(versionNixPath)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertVersionAtTop_ImprovedRobustness(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
changelogPath := filepath.Join(tempDir, "CHANGELOG.md")
|
||||
|
||||
cfg := &config.Config{
|
||||
RepoPath: tempDir,
|
||||
}
|
||||
|
||||
g := &Generator{cfg: cfg}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
existingContent string
|
||||
entry string
|
||||
expectedContent string
|
||||
}{
|
||||
{
|
||||
name: "header with trailing spaces",
|
||||
existingContent: "# Changelog \n\n## v1.0.0\n- Old content",
|
||||
entry: "## v2.0.0\n- New content",
|
||||
expectedContent: "# Changelog \n\n## v2.0.0\n- New content\n## v1.0.0\n- Old content",
|
||||
},
|
||||
{
|
||||
name: "header with different line endings",
|
||||
existingContent: "# Changelog\r\n\r\n## v1.0.0\r\n- Old content",
|
||||
entry: "## v2.0.0\n- New content",
|
||||
expectedContent: "# Changelog\r\n\r\n## v2.0.0\n- New content\n## v1.0.0\r\n- Old content",
|
||||
},
|
||||
{
|
||||
name: "no existing header",
|
||||
existingContent: "Some existing content without header",
|
||||
entry: "## v1.0.0\n- New content",
|
||||
expectedContent: "# Changelog\n\n## v1.0.0\n- New content\n\nSome existing content without header",
|
||||
},
|
||||
{
|
||||
name: "new file creation",
|
||||
existingContent: "",
|
||||
entry: "## v1.0.0\n- Initial release",
|
||||
expectedContent: "# Changelog\n\n## v1.0.0\n- Initial release\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Write existing content (or create empty file)
|
||||
if tt.existingContent != "" {
|
||||
if err := os.WriteFile(changelogPath, []byte(tt.existingContent), 0644); err != nil {
|
||||
t.Fatalf("Failed to write existing content: %v", err)
|
||||
}
|
||||
} else {
|
||||
// Remove file if it exists to test new file creation
|
||||
os.Remove(changelogPath)
|
||||
}
|
||||
|
||||
// Insert new version
|
||||
if err := g.insertVersionAtTop(tt.entry); err != nil {
|
||||
t.Fatalf("insertVersionAtTop failed: %v", err)
|
||||
}
|
||||
|
||||
// Read result
|
||||
result, err := os.ReadFile(changelogPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read result: %v", err)
|
||||
}
|
||||
|
||||
if string(result) != tt.expectedContent {
|
||||
t.Errorf("Expected:\n%q\nGot:\n%q", tt.expectedContent, string(result))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessIncomingPRs_FileAggregation(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
incomingDir := filepath.Join(tempDir, "incoming")
|
||||
|
||||
// Create incoming directory and files
|
||||
if err := os.MkdirAll(incomingDir, 0755); err != nil {
|
||||
t.Fatalf("Failed to create incoming dir: %v", err)
|
||||
}
|
||||
|
||||
// Create test incoming files
|
||||
file1Content := "## PR #1\n- Feature A"
|
||||
file2Content := "## PR #2\n- Feature B"
|
||||
|
||||
if err := os.WriteFile(filepath.Join(incomingDir, "1.txt"), []byte(file1Content), 0644); err != nil {
|
||||
t.Fatalf("Failed to create test file: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(incomingDir, "2.txt"), []byte(file2Content), 0644); err != nil {
|
||||
t.Fatalf("Failed to create test file: %v", err)
|
||||
}
|
||||
|
||||
// Test file aggregation logic by calling the internal functions
|
||||
files, err := filepath.Glob(filepath.Join(incomingDir, "*.txt"))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to glob files: %v", err)
|
||||
}
|
||||
|
||||
if len(files) != 2 {
|
||||
t.Fatalf("Expected 2 files, got %d", len(files))
|
||||
}
|
||||
|
||||
// Test content aggregation
|
||||
var content strings.Builder
|
||||
var processingErrors []string
|
||||
for _, file := range files {
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
processingErrors = append(processingErrors, err.Error())
|
||||
continue
|
||||
}
|
||||
content.WriteString(string(data))
|
||||
content.WriteString("\n")
|
||||
}
|
||||
|
||||
if len(processingErrors) > 0 {
|
||||
t.Fatalf("Unexpected processing errors: %v", processingErrors)
|
||||
}
|
||||
|
||||
aggregatedContent := content.String()
|
||||
if !strings.Contains(aggregatedContent, "Feature A") {
|
||||
t.Errorf("Aggregated content should contain 'Feature A'")
|
||||
}
|
||||
if !strings.Contains(aggregatedContent, "Feature B") {
|
||||
t.Errorf("Aggregated content should contain 'Feature B'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileProcessing_ErrorHandling(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
incomingDir := filepath.Join(tempDir, "incoming")
|
||||
|
||||
// Create incoming directory with one good file and one unreadable file
|
||||
if err := os.MkdirAll(incomingDir, 0755); err != nil {
|
||||
t.Fatalf("Failed to create incoming dir: %v", err)
|
||||
}
|
||||
|
||||
// Create a good file
|
||||
if err := os.WriteFile(filepath.Join(incomingDir, "1.txt"), []byte("content"), 0644); err != nil {
|
||||
t.Fatalf("Failed to create test file: %v", err)
|
||||
}
|
||||
|
||||
// Create an unreadable file (simulate permission error)
|
||||
unreadableFile := filepath.Join(incomingDir, "2.txt")
|
||||
if err := os.WriteFile(unreadableFile, []byte("content"), 0000); err != nil {
|
||||
t.Fatalf("Failed to create unreadable file: %v", err)
|
||||
}
|
||||
defer os.Chmod(unreadableFile, 0644) // Clean up
|
||||
|
||||
// Test error aggregation logic
|
||||
files, err := filepath.Glob(filepath.Join(incomingDir, "*.txt"))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to glob files: %v", err)
|
||||
}
|
||||
|
||||
var content strings.Builder
|
||||
var processingErrors []string
|
||||
for _, file := range files {
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
processingErrors = append(processingErrors, err.Error())
|
||||
continue
|
||||
}
|
||||
content.WriteString(string(data))
|
||||
content.WriteString("\n")
|
||||
}
|
||||
|
||||
if len(processingErrors) == 0 {
|
||||
t.Errorf("Expected processing errors due to unreadable file")
|
||||
}
|
||||
|
||||
// Verify error message format
|
||||
errorMsg := strings.Join(processingErrors, "; ")
|
||||
if !strings.Contains(errorMsg, "2.txt") {
|
||||
t.Errorf("Error message should mention the problematic file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureIncomingDirCreation(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
incomingDir := filepath.Join(tempDir, "incoming")
|
||||
|
||||
cfg := &config.Config{
|
||||
IncomingDir: incomingDir,
|
||||
}
|
||||
|
||||
g := &Generator{cfg: cfg}
|
||||
|
||||
err := g.ensureIncomingDir()
|
||||
if err != nil {
|
||||
t.Fatalf("ensureIncomingDir failed: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(incomingDir); os.IsNotExist(err) {
|
||||
t.Errorf("Incoming directory was not created")
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,21 @@
|
||||
package config
|
||||
|
||||
type Config struct {
|
||||
RepoPath string
|
||||
OutputFile string
|
||||
Limit int
|
||||
Version string
|
||||
SaveData bool
|
||||
CacheFile string
|
||||
NoCache bool
|
||||
RebuildCache bool
|
||||
GitHubToken string
|
||||
ForcePRSync bool
|
||||
EnableAISummary bool
|
||||
RepoPath string
|
||||
OutputFile string
|
||||
Limit int
|
||||
Version string
|
||||
SaveData bool
|
||||
CacheFile string
|
||||
NoCache bool
|
||||
RebuildCache bool
|
||||
GitHubToken string
|
||||
ForcePRSync bool
|
||||
EnableAISummary bool
|
||||
IncomingPR int
|
||||
ProcessPRsVersion string
|
||||
IncomingDir string
|
||||
Push bool
|
||||
SyncDB bool
|
||||
Release string
|
||||
}
|
||||
|
||||
@@ -7,14 +7,24 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/danielmiessler/fabric/cmd/generate_changelog/util"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-git/go-git/v5/plumbing/storer"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
)
|
||||
|
||||
var (
|
||||
versionPattern = regexp.MustCompile(`Update version to (v\d+\.\d+\.\d+)`)
|
||||
// The versionPattern matches version commit messages with or without the optional "chore(release): " prefix.
|
||||
// Examples of matching commit messages:
|
||||
// - "chore(release): Update version to v1.2.3"
|
||||
// - "Update version to v1.2.3"
|
||||
// Examples of non-matching commit messages:
|
||||
// - "fix: Update version to v1.2.3" (missing "chore(release): " or "Update version to")
|
||||
// - "chore(release): Update version to 1.2.3" (missing "v" prefix in version)
|
||||
// - "Update version to v1.2" (incomplete version number)
|
||||
versionPattern = regexp.MustCompile(`(?:chore\(release\): )?Update version to (v\d+\.\d+\.\d+)`)
|
||||
prPattern = regexp.MustCompile(`Merge pull request #(\d+)`)
|
||||
)
|
||||
|
||||
@@ -400,3 +410,165 @@ func dedupInts(ints []int) []int {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Worktree returns the git worktree for performing git operations
|
||||
func (w *Walker) Worktree() (*git.Worktree, error) {
|
||||
return w.repo.Worktree()
|
||||
}
|
||||
|
||||
// Repository returns the underlying git repository
|
||||
func (w *Walker) Repository() *git.Repository {
|
||||
return w.repo
|
||||
}
|
||||
|
||||
// IsWorkingDirectoryClean checks if the working directory has any uncommitted changes
|
||||
func (w *Walker) IsWorkingDirectoryClean() (bool, error) {
|
||||
worktree, err := w.repo.Worktree()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to get worktree: %w", err)
|
||||
}
|
||||
|
||||
status, err := worktree.Status()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to get git status: %w", err)
|
||||
}
|
||||
|
||||
return status.IsClean(), nil
|
||||
}
|
||||
|
||||
// GetStatusDetails returns a detailed status of the working directory
|
||||
func (w *Walker) GetStatusDetails() (string, error) {
|
||||
worktree, err := w.repo.Worktree()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get worktree: %w", err)
|
||||
}
|
||||
|
||||
status, err := worktree.Status()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get git status: %w", err)
|
||||
}
|
||||
|
||||
if status.IsClean() {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var details strings.Builder
|
||||
for file, fileStatus := range status {
|
||||
details.WriteString(fmt.Sprintf(" %c%c %s\n", fileStatus.Staging, fileStatus.Worktree, file))
|
||||
}
|
||||
|
||||
return details.String(), nil
|
||||
}
|
||||
|
||||
// AddFile adds a file to the git index
|
||||
func (w *Walker) AddFile(filename string) error {
|
||||
worktree, err := w.repo.Worktree()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get worktree: %w", err)
|
||||
}
|
||||
|
||||
_, err = worktree.Add(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add file %s: %w", filename, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommitChanges creates a commit with the given message
|
||||
func (w *Walker) CommitChanges(message string) (plumbing.Hash, error) {
|
||||
worktree, err := w.repo.Worktree()
|
||||
if err != nil {
|
||||
return plumbing.ZeroHash, fmt.Errorf("failed to get worktree: %w", err)
|
||||
}
|
||||
|
||||
// Get git config for author information
|
||||
cfg, err := w.repo.Config()
|
||||
if err != nil {
|
||||
return plumbing.ZeroHash, fmt.Errorf("failed to get git config: %w", err)
|
||||
}
|
||||
|
||||
var authorName, authorEmail string
|
||||
if cfg.User.Name != "" {
|
||||
authorName = cfg.User.Name
|
||||
} else {
|
||||
authorName = "Changelog Bot"
|
||||
}
|
||||
if cfg.User.Email != "" {
|
||||
authorEmail = cfg.User.Email
|
||||
} else {
|
||||
authorEmail = "bot@changelog.local"
|
||||
}
|
||||
|
||||
commit, err := worktree.Commit(message, &git.CommitOptions{
|
||||
Author: &object.Signature{
|
||||
Name: authorName,
|
||||
Email: authorEmail,
|
||||
When: time.Now(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return plumbing.ZeroHash, fmt.Errorf("failed to commit: %w", err)
|
||||
}
|
||||
|
||||
return commit, nil
|
||||
}
|
||||
|
||||
// PushToRemote pushes the current branch to the remote repository
|
||||
// It automatically detects GitHub repositories and uses token authentication when available
|
||||
func (w *Walker) PushToRemote() error {
|
||||
pushOptions := &git.PushOptions{}
|
||||
|
||||
// Check if we have a GitHub token for authentication
|
||||
if githubToken := util.GetTokenFromEnv(""); githubToken != "" {
|
||||
// Get remote URL to check if it's a GitHub repository
|
||||
remotes, err := w.repo.Remotes()
|
||||
if err == nil && len(remotes) > 0 {
|
||||
// Get the origin remote (or first remote if origin doesn't exist)
|
||||
var remote *git.Remote
|
||||
for _, r := range remotes {
|
||||
if r.Config().Name == "origin" {
|
||||
remote = r
|
||||
break
|
||||
}
|
||||
}
|
||||
if remote == nil {
|
||||
remote = remotes[0]
|
||||
}
|
||||
|
||||
// Check if this is a GitHub repository
|
||||
urls := remote.Config().URLs
|
||||
if len(urls) > 0 {
|
||||
url := urls[0]
|
||||
if strings.Contains(url, "github.com") {
|
||||
// Use token authentication for GitHub repositories
|
||||
pushOptions.Auth = &http.BasicAuth{
|
||||
Username: "token", // GitHub expects "token" as username
|
||||
Password: githubToken,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := w.repo.Push(pushOptions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to push: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveFile removes a file from both the working directory and git index
|
||||
func (w *Walker) RemoveFile(filename string) error {
|
||||
worktree, err := w.repo.Worktree()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get worktree: %w", err)
|
||||
}
|
||||
|
||||
_, err = worktree.Remove(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove file %s: %w", filename, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -100,35 +100,89 @@ func (c *Client) FetchPRs(prNumbers []int) ([]*PR, error) {
|
||||
return prs, nil
|
||||
}
|
||||
|
||||
func (c *Client) fetchSinglePR(ctx context.Context, prNumber int) (*PR, error) {
|
||||
pr, _, err := c.client.PullRequests.Get(ctx, c.owner, c.repo, prNumber)
|
||||
// GetPRValidationDetails fetches only the data needed for validation (lightweight).
|
||||
func (c *Client) GetPRValidationDetails(prNumber int) (*PRDetails, error) {
|
||||
ctx := context.Background()
|
||||
ghPR, _, err := c.client.PullRequests.Get(ctx, c.owner, c.repo, prNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to get PR %d: %w", prNumber, err)
|
||||
}
|
||||
|
||||
commits, _, err := c.client.PullRequests.ListCommits(ctx, c.owner, c.repo, prNumber, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch commits: %w", err)
|
||||
// Only return validation data, no commits fetched
|
||||
details := &PRDetails{
|
||||
PR: nil, // Will be populated later if needed
|
||||
State: getString(ghPR.State),
|
||||
Mergeable: ghPR.Mergeable != nil && *ghPR.Mergeable,
|
||||
}
|
||||
|
||||
return details, nil
|
||||
}
|
||||
|
||||
// GetPRWithCommits fetches the full PR and its commits.
|
||||
func (c *Client) GetPRWithCommits(prNumber int) (*PR, error) {
|
||||
ctx := context.Background()
|
||||
ghPR, _, err := c.client.PullRequests.Get(ctx, c.owner, c.repo, prNumber)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get PR %d: %w", prNumber, err)
|
||||
}
|
||||
|
||||
return c.buildPRWithCommits(ctx, ghPR)
|
||||
}
|
||||
|
||||
// GetPRDetails fetches a comprehensive set of details for a single PR.
|
||||
// Deprecated: Use GetPRValidationDetails + GetPRWithCommits for better performance
|
||||
func (c *Client) GetPRDetails(prNumber int) (*PRDetails, error) {
|
||||
ctx := context.Background()
|
||||
ghPR, _, err := c.client.PullRequests.Get(ctx, c.owner, c.repo, prNumber)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get PR %d: %w", prNumber, err)
|
||||
}
|
||||
|
||||
// Reuse the existing logic to build the base PR object
|
||||
pr, err := c.buildPRWithCommits(ctx, ghPR)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build PR details for %d: %w", prNumber, err)
|
||||
}
|
||||
|
||||
details := &PRDetails{
|
||||
PR: pr,
|
||||
State: getString(ghPR.State),
|
||||
Mergeable: ghPR.Mergeable != nil && *ghPR.Mergeable,
|
||||
}
|
||||
|
||||
return details, nil
|
||||
}
|
||||
|
||||
// buildPRWithCommits fetches commits and constructs a PR object from a GitHub API response
|
||||
func (c *Client) buildPRWithCommits(ctx context.Context, ghPR *github.PullRequest) (*PR, error) {
|
||||
commits, _, err := c.client.PullRequests.ListCommits(ctx, c.owner, c.repo, *ghPR.Number, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch commits for PR %d: %w", *ghPR.Number, err)
|
||||
}
|
||||
|
||||
return c.convertGitHubPR(ghPR, commits), nil
|
||||
}
|
||||
|
||||
// convertGitHubPR transforms GitHub API data into our internal PR struct (pure function)
|
||||
func (c *Client) convertGitHubPR(ghPR *github.PullRequest, commits []*github.RepositoryCommit) *PR {
|
||||
|
||||
result := &PR{
|
||||
Number: prNumber,
|
||||
Title: getString(pr.Title),
|
||||
Body: getString(pr.Body),
|
||||
URL: getString(pr.HTMLURL),
|
||||
Number: *ghPR.Number,
|
||||
Title: getString(ghPR.Title),
|
||||
Body: getString(ghPR.Body),
|
||||
URL: getString(ghPR.HTMLURL),
|
||||
Commits: make([]PRCommit, 0, len(commits)),
|
||||
}
|
||||
|
||||
if pr.MergedAt != nil {
|
||||
result.MergedAt = pr.MergedAt.Time
|
||||
if ghPR.MergedAt != nil {
|
||||
result.MergedAt = ghPR.MergedAt.Time
|
||||
}
|
||||
|
||||
if pr.User != nil {
|
||||
result.Author = getString(pr.User.Login)
|
||||
result.AuthorURL = getString(pr.User.HTMLURL)
|
||||
userType := getString(pr.User.Type) // GitHub API returns "User", "Organization", or "Bot"
|
||||
if ghPR.User != nil {
|
||||
result.Author = getString(ghPR.User.Login)
|
||||
result.AuthorURL = getString(ghPR.User.HTMLURL)
|
||||
userType := getString(ghPR.User.Type)
|
||||
|
||||
// Convert GitHub API type to lowercase
|
||||
switch userType {
|
||||
case "User":
|
||||
result.AuthorType = "user"
|
||||
@@ -137,12 +191,12 @@ func (c *Client) fetchSinglePR(ctx context.Context, prNumber int) (*PR, error) {
|
||||
case "Bot":
|
||||
result.AuthorType = "bot"
|
||||
default:
|
||||
result.AuthorType = "user" // Default fallback
|
||||
result.AuthorType = "user"
|
||||
}
|
||||
}
|
||||
|
||||
if pr.MergeCommitSHA != nil {
|
||||
result.MergeCommit = *pr.MergeCommitSHA
|
||||
if ghPR.MergeCommitSHA != nil {
|
||||
result.MergeCommit = *ghPR.MergeCommitSHA
|
||||
}
|
||||
|
||||
for _, commit := range commits {
|
||||
@@ -153,12 +207,34 @@ func (c *Client) fetchSinglePR(ctx context.Context, prNumber int) (*PR, error) {
|
||||
}
|
||||
if commit.Commit.Author != nil {
|
||||
prCommit.Author = getString(commit.Commit.Author.Name)
|
||||
prCommit.Email = getString(commit.Commit.Author.Email) // Extract author email from GitHub API response
|
||||
// Capture actual commit timestamp from GitHub API
|
||||
if commit.Commit.Author.Date != nil {
|
||||
prCommit.Date = commit.Commit.Author.Date.Time
|
||||
}
|
||||
}
|
||||
// Capture parent commit SHAs for merge detection
|
||||
if commit.Parents != nil {
|
||||
for _, parent := range commit.Parents {
|
||||
if parent.SHA != nil {
|
||||
prCommit.Parents = append(prCommit.Parents, *parent.SHA)
|
||||
}
|
||||
}
|
||||
}
|
||||
result.Commits = append(result.Commits, prCommit)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *Client) fetchSinglePR(ctx context.Context, prNumber int) (*PR, error) {
|
||||
ghPR, _, err := c.client.PullRequests.Get(ctx, c.owner, c.repo, prNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.buildPRWithCommits(ctx, ghPR)
|
||||
}
|
||||
|
||||
func getString(s *string) string {
|
||||
@@ -332,6 +408,7 @@ func (c *Client) FetchAllMergedPRsGraphQL(since time.Time) ([]*PR, error) {
|
||||
SHA: commitNode.Commit.OID,
|
||||
Message: strings.TrimSpace(commitNode.Commit.Message),
|
||||
Author: commitNode.Commit.Author.Name,
|
||||
Date: commitNode.Commit.AuthoredDate, // Use actual commit timestamp
|
||||
}
|
||||
pr.Commits = append(pr.Commits, commit)
|
||||
}
|
||||
|
||||
59
cmd/generate_changelog/internal/github/email_test.go
Normal file
59
cmd/generate_changelog/internal/github/email_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPRCommitEmailHandling(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
commit PRCommit
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "Valid email field",
|
||||
commit: PRCommit{
|
||||
SHA: "abc123",
|
||||
Message: "Fix bug in authentication",
|
||||
Author: "John Doe",
|
||||
Email: "john.doe@example.com",
|
||||
Date: time.Now(),
|
||||
Parents: []string{"def456"},
|
||||
},
|
||||
expected: "john.doe@example.com",
|
||||
},
|
||||
{
|
||||
name: "Empty email field",
|
||||
commit: PRCommit{
|
||||
SHA: "abc123",
|
||||
Message: "Fix bug in authentication",
|
||||
Author: "John Doe",
|
||||
Email: "",
|
||||
Date: time.Now(),
|
||||
Parents: []string{"def456"},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "Email field with proper initialization",
|
||||
commit: PRCommit{
|
||||
SHA: "def789",
|
||||
Message: "Add new feature",
|
||||
Author: "Jane Smith",
|
||||
Email: "jane.smith@company.org",
|
||||
Date: time.Now(),
|
||||
Parents: []string{"ghi012"},
|
||||
},
|
||||
expected: "jane.smith@company.org",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.commit.Email != tt.expected {
|
||||
t.Errorf("Expected email %q, got %q", tt.expected, tt.commit.Email)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,20 @@ type PR struct {
|
||||
MergeCommit string
|
||||
}
|
||||
|
||||
// PRDetails encapsulates all relevant information about a Pull Request.
|
||||
type PRDetails struct {
|
||||
*PR
|
||||
State string
|
||||
Mergeable bool
|
||||
}
|
||||
|
||||
type PRCommit struct {
|
||||
SHA string
|
||||
Message string
|
||||
Author string
|
||||
Email string // Author email from GitHub API, empty if not public
|
||||
Date time.Time // Timestamp field
|
||||
Parents []string // Parent commits (for merge detection)
|
||||
}
|
||||
|
||||
// GraphQL query structures for hasura client
|
||||
@@ -43,9 +53,10 @@ type PullRequestsQuery struct {
|
||||
Commits struct {
|
||||
Nodes []struct {
|
||||
Commit struct {
|
||||
OID string `graphql:"oid"`
|
||||
Message string
|
||||
Author struct {
|
||||
OID string `graphql:"oid"`
|
||||
Message string
|
||||
AuthoredDate time.Time `graphql:"authoredDate"`
|
||||
Author struct {
|
||||
Name string
|
||||
}
|
||||
}
|
||||
|
||||
149
cmd/generate_changelog/internal/release.go
Normal file
149
cmd/generate_changelog/internal/release.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/cmd/generate_changelog/internal/cache"
|
||||
"github.com/danielmiessler/fabric/cmd/generate_changelog/internal/config"
|
||||
"github.com/google/go-github/v66/github"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type ReleaseManager struct {
|
||||
cache *cache.Cache
|
||||
githubToken string
|
||||
owner string
|
||||
repo string
|
||||
}
|
||||
|
||||
// getGitHubInfo extracts owner and repo from git remote origin URL
|
||||
func getGitHubInfo() (owner, repo string, err error) {
|
||||
cmd := exec.Command("git", "remote", "get-url", "origin")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to get git remote URL: %w", err)
|
||||
}
|
||||
|
||||
url := strings.TrimSpace(string(output))
|
||||
|
||||
// Handle both SSH and HTTPS URLs
|
||||
// SSH: git@github.com:owner/repo.git
|
||||
// HTTPS: https://github.com/owner/repo.git
|
||||
var re *regexp.Regexp
|
||||
if strings.HasPrefix(url, "git@") {
|
||||
re = regexp.MustCompile(`git@github\.com:([^/]+)/([^/.]+)(?:\.git)?`)
|
||||
} else {
|
||||
re = regexp.MustCompile(`https://github\.com/([^/]+)/([^/.]+)(?:\.git)?`)
|
||||
}
|
||||
|
||||
matches := re.FindStringSubmatch(url)
|
||||
if len(matches) < 3 {
|
||||
return "", "", fmt.Errorf("invalid GitHub URL format: %s", url)
|
||||
}
|
||||
|
||||
return matches[1], matches[2], nil
|
||||
}
|
||||
|
||||
func NewReleaseManager(cfg *config.Config) (*ReleaseManager, error) {
|
||||
cache, err := cache.New(cfg.CacheFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create cache: %w", err)
|
||||
}
|
||||
|
||||
owner, repo, err := getGitHubInfo()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get GitHub repository info: %w", err)
|
||||
}
|
||||
|
||||
return &ReleaseManager{
|
||||
cache: cache,
|
||||
githubToken: cfg.GitHubToken,
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rm *ReleaseManager) Close() error {
|
||||
return rm.cache.Close()
|
||||
}
|
||||
|
||||
func (rm *ReleaseManager) UpdateReleaseDescription(version string) error {
|
||||
versions, err := rm.cache.GetVersions()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get versions from cache: %w", err)
|
||||
}
|
||||
|
||||
versionData, exists := versions[version]
|
||||
if !exists {
|
||||
return fmt.Errorf("version %s not found in versions table", version)
|
||||
}
|
||||
|
||||
if versionData.AISummary == "" {
|
||||
return fmt.Errorf("ai_summary is empty for version %s", version)
|
||||
}
|
||||
|
||||
releaseBody := fmt.Sprintf("## Changes\n\n%s", versionData.AISummary)
|
||||
|
||||
ctx := context.Background()
|
||||
var client *github.Client
|
||||
|
||||
if rm.githubToken != "" {
|
||||
ts := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: rm.githubToken},
|
||||
)
|
||||
tc := oauth2.NewClient(ctx, ts)
|
||||
client = github.NewClient(tc)
|
||||
} else {
|
||||
client = github.NewClient(nil)
|
||||
}
|
||||
|
||||
// Check if current repository is a fork by getting repo details
|
||||
repo, _, err := client.Repositories.Get(ctx, rm.owner, rm.repo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get repository info: %w", err)
|
||||
}
|
||||
|
||||
// If repository is a fork, try updating the upstream (parent) repository first
|
||||
if repo.Parent != nil {
|
||||
parentOwner := repo.Parent.Owner.GetLogin()
|
||||
parentRepo := repo.Parent.GetName()
|
||||
|
||||
fmt.Printf("Repository is a fork of %s/%s, attempting to update upstream release...\n", parentOwner, parentRepo)
|
||||
|
||||
err := rm.updateReleaseForRepo(ctx, client, parentOwner, parentRepo, version, releaseBody)
|
||||
if err == nil {
|
||||
fmt.Printf("Successfully updated release description for %s in upstream repository %s/%s\n", version, parentOwner, parentRepo)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Failed to update upstream repository: %v\nFalling back to current repository...\n", err)
|
||||
}
|
||||
|
||||
// Update current repository (either not a fork or upstream update failed)
|
||||
err = rm.updateReleaseForRepo(ctx, client, rm.owner, rm.repo, version, releaseBody)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update release description for version %s in repository %s/%s: %w", version, rm.owner, rm.repo, err)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully updated release description for %s in repository %s/%s\n", version, rm.owner, rm.repo)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rm *ReleaseManager) updateReleaseForRepo(ctx context.Context, client *github.Client, owner, repo, version, releaseBody string) error {
|
||||
release, _, err := client.Repositories.GetReleaseByTag(ctx, owner, repo, version)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get release for version %s: %w", version, err)
|
||||
}
|
||||
|
||||
release.Body = &releaseBody
|
||||
_, _, err = client.Repositories.EditRelease(ctx, owner, repo, *release.ID, release)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update release description for version %s: %w", version, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -5,8 +5,10 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/danielmiessler/fabric/cmd/generate_changelog/internal"
|
||||
"github.com/danielmiessler/fabric/cmd/generate_changelog/internal/changelog"
|
||||
"github.com/danielmiessler/fabric/cmd/generate_changelog/internal/config"
|
||||
"github.com/danielmiessler/fabric/cmd/generate_changelog/util"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -21,7 +23,8 @@ var rootCmd = &cobra.Command{
|
||||
Long: `A high-performance changelog generator that walks git history,
|
||||
collects version information and pull requests, and generates a
|
||||
comprehensive changelog in markdown format.`,
|
||||
RunE: run,
|
||||
RunE: run,
|
||||
SilenceUsage: true, // Don't show usage on runtime errors, only on flag errors
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -36,18 +39,51 @@ func init() {
|
||||
rootCmd.Flags().StringVar(&cfg.GitHubToken, "token", "", "GitHub API token (or set GITHUB_TOKEN env var)")
|
||||
rootCmd.Flags().BoolVar(&cfg.ForcePRSync, "force-pr-sync", false, "Force a full PR sync from GitHub (ignores cache age)")
|
||||
rootCmd.Flags().BoolVar(&cfg.EnableAISummary, "ai-summarize", false, "Generate AI-enhanced summaries using Fabric")
|
||||
rootCmd.Flags().IntVar(&cfg.IncomingPR, "incoming-pr", 0, "Pre-process PR for changelog (provide PR number)")
|
||||
rootCmd.Flags().StringVar(&cfg.ProcessPRsVersion, "process-prs", "", "Process all incoming PR files for release (provide version like v1.4.262)")
|
||||
rootCmd.Flags().StringVar(&cfg.IncomingDir, "incoming-dir", "./cmd/generate_changelog/incoming", "Directory for incoming PR files")
|
||||
rootCmd.Flags().BoolVar(&cfg.Push, "push", false, "Enable automatic git push after creating an incoming entry")
|
||||
rootCmd.Flags().BoolVar(&cfg.SyncDB, "sync-db", false, "Synchronize and validate database integrity with git history and GitHub PRs")
|
||||
rootCmd.Flags().StringVar(&cfg.Release, "release", "", "Update GitHub release description with AI summary for version (e.g., v1.2.3)")
|
||||
}
|
||||
|
||||
func run(cmd *cobra.Command, args []string) error {
|
||||
if cfg.GitHubToken == "" {
|
||||
cfg.GitHubToken = os.Getenv("GITHUB_TOKEN")
|
||||
if cfg.IncomingPR > 0 && cfg.ProcessPRsVersion != "" {
|
||||
return fmt.Errorf("--incoming-pr and --process-prs are mutually exclusive flags")
|
||||
}
|
||||
|
||||
if cfg.Release != "" && (cfg.IncomingPR > 0 || cfg.ProcessPRsVersion != "" || cfg.SyncDB) {
|
||||
return fmt.Errorf("--release cannot be used with other processing flags")
|
||||
}
|
||||
|
||||
cfg.GitHubToken = util.GetTokenFromEnv(cfg.GitHubToken)
|
||||
|
||||
generator, err := changelog.New(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create changelog generator: %w", err)
|
||||
}
|
||||
|
||||
if cfg.IncomingPR > 0 {
|
||||
return generator.ProcessIncomingPR(cfg.IncomingPR)
|
||||
}
|
||||
|
||||
if cfg.ProcessPRsVersion != "" {
|
||||
return generator.CreateNewChangelogEntry(cfg.ProcessPRsVersion)
|
||||
}
|
||||
|
||||
if cfg.SyncDB {
|
||||
return generator.SyncDatabase()
|
||||
}
|
||||
|
||||
if cfg.Release != "" {
|
||||
releaseManager, err := internal.NewReleaseManager(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create release manager: %w", err)
|
||||
}
|
||||
defer releaseManager.Close()
|
||||
return releaseManager.UpdateReleaseDescription(cfg.Release)
|
||||
}
|
||||
|
||||
output, err := generator.Generate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate changelog: %w", err)
|
||||
@@ -77,8 +113,5 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
rootCmd.Execute()
|
||||
}
|
||||
|
||||
31
cmd/generate_changelog/util/token.go
Normal file
31
cmd/generate_changelog/util/token.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// GetTokenFromEnv returns a GitHub token based on the following precedence order:
|
||||
// 1. If tokenValue is non-empty, it is returned.
|
||||
// 2. Otherwise, if the GITHUB_TOKEN environment variable is set, its value is returned.
|
||||
// 3. Otherwise, if the GH_TOKEN environment variable is set, its value is returned.
|
||||
// 4. If none of the above are set, an empty string is returned.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// os.Setenv("GITHUB_TOKEN", "abc")
|
||||
// os.Setenv("GH_TOKEN", "def")
|
||||
// GetTokenFromEnv("xyz") // returns "xyz"
|
||||
// GetTokenFromEnv("") // returns "abc"
|
||||
// os.Unsetenv("GITHUB_TOKEN")
|
||||
// GetTokenFromEnv("") // returns "def"
|
||||
// os.Unsetenv("GH_TOKEN")
|
||||
// GetTokenFromEnv("") // returns ""
|
||||
func GetTokenFromEnv(tokenValue string) string {
|
||||
if tokenValue == "" {
|
||||
tokenValue = os.Getenv("GITHUB_TOKEN")
|
||||
if tokenValue == "" {
|
||||
tokenValue = os.Getenv("GH_TOKEN")
|
||||
}
|
||||
}
|
||||
return tokenValue
|
||||
}
|
||||
@@ -1,47 +1,69 @@
|
||||
#compdef fabric
|
||||
#compdef fabric fabric-ai
|
||||
|
||||
# Zsh completion for fabric CLI
|
||||
# Place this file in a directory in your $fpath (e.g. /usr/local/share/zsh/site-functions)
|
||||
|
||||
_fabric_patterns() {
|
||||
local -a patterns
|
||||
patterns=(${(f)"$(fabric --listpatterns --shell-complete-list 2>/dev/null)"})
|
||||
local cmd=${words[1]}
|
||||
patterns=(${(f)"$($cmd --listpatterns --shell-complete-list 2>/dev/null)"})
|
||||
compadd -X "Patterns:" ${patterns}
|
||||
}
|
||||
|
||||
_fabric_models() {
|
||||
local -a models
|
||||
models=(${(f)"$(fabric --listmodels --shell-complete-list 2>/dev/null)"})
|
||||
local cmd=${words[1]}
|
||||
models=(${(f)"$($cmd --listmodels --shell-complete-list 2>/dev/null)"})
|
||||
compadd -X "Models:" ${models}
|
||||
}
|
||||
|
||||
_fabric_vendors() {
|
||||
local -a vendors
|
||||
local cmd=${words[1]}
|
||||
vendors=(${(f)"$($cmd --listvendors --shell-complete-list 2>/dev/null)"})
|
||||
compadd -X "Vendors:" ${vendors}
|
||||
}
|
||||
|
||||
_fabric_contexts() {
|
||||
local -a contexts
|
||||
contexts=(${(f)"$(fabric --listcontexts --shell-complete-list 2>/dev/null)"})
|
||||
local cmd=${words[1]}
|
||||
contexts=(${(f)"$($cmd --listcontexts --shell-complete-list 2>/dev/null)"})
|
||||
compadd -X "Contexts:" ${contexts}
|
||||
}
|
||||
|
||||
_fabric_sessions() {
|
||||
local -a sessions
|
||||
sessions=(${(f)"$(fabric --listsessions --shell-complete-list 2>/dev/null)"})
|
||||
local cmd=${words[1]}
|
||||
sessions=(${(f)"$($cmd --listsessions --shell-complete-list 2>/dev/null)"})
|
||||
compadd -X "Sessions:" ${sessions}
|
||||
}
|
||||
|
||||
_fabric_strategies() {
|
||||
local -a strategies
|
||||
strategies=(${(f)"$(fabric --liststrategies --shell-complete-list 2>/dev/null)"})
|
||||
local cmd=${words[1]}
|
||||
strategies=(${(f)"$($cmd --liststrategies --shell-complete-list 2>/dev/null)"})
|
||||
compadd -X "Strategies:" ${strategies}
|
||||
}
|
||||
|
||||
_fabric_extensions() {
|
||||
local -a extensions
|
||||
extensions=(${(f)"$(fabric --listextensions --shell-complete-list 2>/dev/null)"})
|
||||
local cmd=${words[1]}
|
||||
extensions=(${(f)"$($cmd --listextensions --shell-complete-list 2>/dev/null)"})
|
||||
compadd -X "Extensions:" ${extensions}
|
||||
'(-L --listmodels)'{-L,--listmodels}'[List all available models]:list models:_fabric_models' \
|
||||
'(-x --listcontexts)'{-x,--listcontexts}'[List all contexts]:list contexts:_fabric_contexts' \
|
||||
'(-X --listsessions)'{-X,--listsessions}'[List all sessions]:list sessions:_fabric_sessions' \
|
||||
'(--listextensions)--listextensions[List all registered extensions]' \
|
||||
'(--liststrategies)--liststrategies[List all strategies]:list strategies:_fabric_strategies' \
|
||||
'(--listvendors)--listvendors[List all vendors]' \
|
||||
vendors=(${(f)"$(fabric --listvendors 2>/dev/null)"})
|
||||
compadd -X "Vendors:" ${vendors}
|
||||
}
|
||||
|
||||
_fabric_gemini_voices() {
|
||||
local -a voices
|
||||
local cmd=${words[1]}
|
||||
voices=(${(f)"$($cmd --list-gemini-voices --shell-complete-list 2>/dev/null)"})
|
||||
compadd -X "Gemini TTS Voices:" ${voices}
|
||||
}
|
||||
|
||||
_fabric_transcription_models() {
|
||||
local -a models
|
||||
local cmd=${words[1]}
|
||||
models=(${(f)"$($cmd --list-transcription-models --shell-complete-list 2>/dev/null)"})
|
||||
compadd -X "Transcription Models:" ${models}
|
||||
}
|
||||
|
||||
_fabric() {
|
||||
@@ -68,6 +90,7 @@ _fabric() {
|
||||
'(-U --updatepatterns)'{-U,--updatepatterns}'[Update patterns]' \
|
||||
'(-c --copy)'{-c,--copy}'[Copy to clipboard]' \
|
||||
'(-m --model)'{-m,--model}'[Choose model]:model:_fabric_models' \
|
||||
'(-V --vendor)'{-V,--vendor}'[Specify vendor for chosen model (e.g., -V "LM Studio" -m openai/gpt-oss-20b)]:vendor:_fabric_vendors' \
|
||||
'(--modelContextLength)--modelContextLength[Model context length (only affects ollama)]:length:' \
|
||||
'(-o --output)'{-o,--output}'[Output to file]:file:_files' \
|
||||
'(--output-session)--output-session[Output the entire session to the output file]' \
|
||||
@@ -79,16 +102,19 @@ _fabric() {
|
||||
'(--transcript-with-timestamps)--transcript-with-timestamps[Grab transcript from YouTube video with timestamps]' \
|
||||
'(--comments)--comments[Grab comments from YouTube video and send to chat]' \
|
||||
'(--metadata)--metadata[Output video metadata]' \
|
||||
'(--yt-dlp-args)--yt-dlp-args[Additional arguments to pass to yt-dlp]:yt-dlp args:' \
|
||||
'(-g --language)'{-g,--language}'[Specify the Language Code for the chat, e.g. -g=en -g=zh]:language:' \
|
||||
'(-u --scrape_url)'{-u,--scrape_url}'[Scrape website URL to markdown using Jina AI]:url:' \
|
||||
'(-q --scrape_question)'{-q,--scrape_question}'[Search question using Jina AI]:question:' \
|
||||
'(-e --seed)'{-e,--seed}'[Seed to be used for LMM generation]:seed:' \
|
||||
'(--thinking)--thinking[Set reasoning/thinking level]:level:(off low medium high)' \
|
||||
'(-w --wipecontext)'{-w,--wipecontext}'[Wipe context]:context:_fabric_contexts' \
|
||||
'(-W --wipesession)'{-W,--wipesession}'[Wipe session]:session:_fabric_sessions' \
|
||||
'(--printcontext)--printcontext[Print context]:context:_fabric_contexts' \
|
||||
'(--printsession)--printsession[Print session]:session:_fabric_sessions' \
|
||||
'(--readability)--readability[Convert HTML input into a clean, readable view]' \
|
||||
'(--input-has-vars)--input-has-vars[Apply variables to user input]' \
|
||||
'(--no-variable-replacement)--no-variable-replacement[Disable pattern variable replacement]' \
|
||||
'(--dry-run)--dry-run[Show what would be sent to the model without actually sending it]' \
|
||||
'(--serve)--serve[Serve the Fabric Rest API]' \
|
||||
'(--serveOllama)--serveOllama[Serve the Fabric Rest API with ollama endpoints]' \
|
||||
@@ -96,7 +122,7 @@ _fabric() {
|
||||
'(--api-key)--api-key[API key used to secure server routes]:api-key:' \
|
||||
'(--config)--config[Path to YAML config file]:config file:_files -g "*.yaml *.yml"' \
|
||||
'(--version)--version[Print current version]' \
|
||||
'(--search)--search[Enable web search tool for supported models (Anthropic, OpenAI)]' \
|
||||
'(--search)--search[Enable web search tool for supported models (Anthropic, OpenAI, Gemini)]' \
|
||||
'(--search-location)--search-location[Set location for web search results]:location:' \
|
||||
'(--image-file)--image-file[Save generated image to specified file path]:image file:_files -g "*.png *.webp *.jpeg *.jpg"' \
|
||||
'(--image-size)--image-size[Image dimensions]:size:(1024x1024 1536x1024 1024x1536 auto)' \
|
||||
@@ -109,13 +135,21 @@ _fabric() {
|
||||
'(--strategy)--strategy[Choose a strategy from the available strategies]:strategy:_fabric_strategies' \
|
||||
'(--liststrategies)--liststrategies[List all strategies]' \
|
||||
'(--listvendors)--listvendors[List all vendors]' \
|
||||
'(--voice)--voice[TTS voice name for supported models]:voice:_fabric_gemini_voices' \
|
||||
'(--list-gemini-voices)--list-gemini-voices[List all available Gemini TTS voices]' \
|
||||
'(--shell-complete-list)--shell-complete-list[Output raw list without headers/formatting (for shell completion)]' \
|
||||
'(--suppress-think)--suppress-think[Suppress text enclosed in thinking tags]' \
|
||||
'(--think-start-tag)--think-start-tag[Start tag for thinking sections (default: <think>)]:start tag:' \
|
||||
'(--think-end-tag)--think-end-tag[End tag for thinking sections (default: </think>)]:end tag:' \
|
||||
'(--disable-responses-api)--disable-responses-api[Disable OpenAI Responses API (default: false)]' \
|
||||
'(--transcribe-file)--transcribe-file[Audio or video file to transcribe]:audio file:_files -g "*.mp3 *.mp4 *.mpeg *.mpga *.m4a *.wav *.webm"' \
|
||||
'(--transcribe-model)--transcribe-model[Model to use for transcription (separate from chat model)]:transcribe model:_fabric_transcription_models' \
|
||||
'(--split-media-file)--split-media-file[Split audio/video files larger than 25MB using ffmpeg]' \
|
||||
'(--debug)--debug[Set debug level (0=off, 1=basic, 2=detailed, 3=trace)]:debug level:(0 1 2 3)' \
|
||||
'(--notification)--notification[Send desktop notification when command completes]' \
|
||||
'(--notification-command)--notification-command[Custom command to run for notifications]:notification command:' \
|
||||
'(-h --help)'{-h,--help}'[Show this help message]' \
|
||||
'*:arguments:'
|
||||
}
|
||||
|
||||
_fabric "$@"
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@ _fabric() {
|
||||
_get_comp_words_by_ref -n : cur prev words cword
|
||||
|
||||
# Define all possible options/flags
|
||||
local opts="--pattern -p --variable -v --context -C --session --attachment -a --setup -S --temperature -t --topp -T --stream -s --presencepenalty -P --raw -r --frequencypenalty -F --listpatterns -l --listmodels -L --listcontexts -x --listsessions -X --updatepatterns -U --copy -c --model -m --modelContextLength --output -o --output-session --latest -n --changeDefaultModel -d --youtube -y --playlist --transcript --transcript-with-timestamps --comments --metadata --language -g --scrape_url -u --scrape_question -q --seed -e --wipecontext -w --wipesession -W --printcontext --printsession --readability --input-has-vars --dry-run --serve --serveOllama --address --api-key --config --search --search-location --image-file --image-size --image-quality --image-compression --image-background --suppress-think --think-start-tag --think-end-tag --version --listextensions --addextension --rmextension --strategy --liststrategies --listvendors --shell-complete-list --help -h"
|
||||
local opts="--pattern -p --variable -v --context -C --session --attachment -a --setup -S --temperature -t --topp -T --stream -s --presencepenalty -P --raw -r --frequencypenalty -F --listpatterns -l --listmodels -L --listcontexts -x --listsessions -X --updatepatterns -U --copy -c --model -m --vendor -V --modelContextLength --output -o --output-session --latest -n --changeDefaultModel -d --youtube -y --playlist --transcript --transcript-with-timestamps --comments --metadata --yt-dlp-args --language -g --scrape_url -u --scrape_question -q --seed -e --thinking --wipecontext -w --wipesession -W --printcontext --printsession --readability --input-has-vars --no-variable-replacement --dry-run --serve --serveOllama --address --api-key --config --search --search-location --image-file --image-size --image-quality --image-compression --image-background --suppress-think --think-start-tag --think-end-tag --disable-responses-api --transcribe-file --transcribe-model --split-media-file --voice --list-gemini-voices --notification --notification-command --debug --version --listextensions --addextension --rmextension --strategy --liststrategies --listvendors --shell-complete-list --help -h"
|
||||
|
||||
# Helper function for dynamic completions
|
||||
_fabric_get_list() {
|
||||
fabric "$1" --shell-complete-list 2>/dev/null
|
||||
"${COMP_WORDS[0]}" "$1" --shell-complete-list 2>/dev/null
|
||||
}
|
||||
|
||||
# Handle completions based on the previous word
|
||||
@@ -38,6 +38,10 @@ _fabric() {
|
||||
COMPREPLY=($(compgen -W "$(_fabric_get_list --listmodels)" -- "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
-V | --vendor)
|
||||
COMPREPLY=($(compgen -W "$(_fabric_get_list --listvendors)" -- "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
-w | --wipecontext)
|
||||
COMPREPLY=($(compgen -W "$(_fabric_get_list --listcontexts)" -- "${cur}"))
|
||||
return 0
|
||||
@@ -54,6 +58,10 @@ _fabric() {
|
||||
COMPREPLY=($(compgen -W "$(_fabric_get_list --listsessions)" -- "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--thinking)
|
||||
COMPREPLY=($(compgen -W "off low medium high" -- "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--rmextension)
|
||||
COMPREPLY=($(compgen -W "$(_fabric_get_list --listextensions)" -- "${cur}"))
|
||||
return 0
|
||||
@@ -62,8 +70,20 @@ _fabric() {
|
||||
COMPREPLY=($(compgen -W "$(_fabric_get_list --liststrategies)" -- "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--voice)
|
||||
COMPREPLY=($(compgen -W "$(_fabric_get_list --list-gemini-voices)" -- "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--transcribe-model)
|
||||
COMPREPLY=($(compgen -W "$(_fabric_get_list --list-transcription-models)" -- "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
--debug)
|
||||
COMPREPLY=($(compgen -W "0 1 2 3" -- "${cur}"))
|
||||
return 0
|
||||
;;
|
||||
# Options requiring file/directory paths
|
||||
-a | --attachment | -o | --output | --config | --addextension | --image-file)
|
||||
-a | --attachment | -o | --output | --config | --addextension | --image-file | --transcribe-file)
|
||||
_filedir
|
||||
return 0
|
||||
;;
|
||||
@@ -81,7 +101,7 @@ _fabric() {
|
||||
return 0
|
||||
;;
|
||||
# Options requiring simple arguments (no specific completion logic here)
|
||||
-v | --variable | -t | --temperature | -T | --topp | -P | --presencepenalty | -F | --frequencypenalty | --modelContextLength | -n | --latest | -y | --youtube | -g | --language | -u | --scrape_url | -q | --scrape_question | -e | --seed | --address | --api-key | --search-location | --image-compression | --think-start-tag | --think-end-tag)
|
||||
-v | --variable | -t | --temperature | -T | --topp | -P | --presencepenalty | -F | --frequencypenalty | --modelContextLength | -n | --latest | -y | --youtube | --yt-dlp-args | -g | --language | -u | --scrape_url | -q | --scrape_question | -e | --seed | --address | --api-key | --search-location | --image-compression | --think-start-tag | --think-end-tag | --notification-command)
|
||||
# No specific completion suggestions, user types the value
|
||||
return 0
|
||||
;;
|
||||
@@ -100,4 +120,4 @@ _fabric() {
|
||||
|
||||
}
|
||||
|
||||
complete -F _fabric fabric
|
||||
complete -F _fabric fabric fabric-ai
|
||||
|
||||
@@ -8,97 +8,137 @@
|
||||
|
||||
# Helper functions for dynamic completions
|
||||
function __fabric_get_patterns
|
||||
fabric --listpatterns --shell-complete-list 2>/dev/null
|
||||
set cmd (commandline -opc)[1]
|
||||
$cmd --listpatterns --shell-complete-list 2>/dev/null
|
||||
end
|
||||
|
||||
function __fabric_get_models
|
||||
fabric --listmodels --shell-complete-list 2>/dev/null
|
||||
set cmd (commandline -opc)[1]
|
||||
$cmd --listmodels --shell-complete-list 2>/dev/null
|
||||
end
|
||||
|
||||
function __fabric_get_vendors
|
||||
set cmd (commandline -opc)[1]
|
||||
$cmd --listvendors --shell-complete-list 2>/dev/null
|
||||
end
|
||||
|
||||
function __fabric_get_contexts
|
||||
fabric --listcontexts --shell-complete-list 2>/dev/null
|
||||
set cmd (commandline -opc)[1]
|
||||
$cmd --listcontexts --shell-complete-list 2>/dev/null
|
||||
end
|
||||
|
||||
function __fabric_get_sessions
|
||||
fabric --listsessions --shell-complete-list 2>/dev/null
|
||||
set cmd (commandline -opc)[1]
|
||||
$cmd --listsessions --shell-complete-list 2>/dev/null
|
||||
end
|
||||
|
||||
function __fabric_get_strategies
|
||||
fabric --liststrategies --shell-complete-list 2>/dev/null
|
||||
set cmd (commandline -opc)[1]
|
||||
$cmd --liststrategies --shell-complete-list 2>/dev/null
|
||||
end
|
||||
|
||||
function __fabric_get_extensions
|
||||
fabric --listextensions --shell-complete-list 2>/dev/null
|
||||
set cmd (commandline -opc)[1]
|
||||
$cmd --listextensions --shell-complete-list 2>/dev/null
|
||||
end
|
||||
|
||||
function __fabric_get_gemini_voices
|
||||
set cmd (commandline -opc)[1]
|
||||
$cmd --list-gemini-voices --shell-complete-list 2>/dev/null
|
||||
end
|
||||
|
||||
function __fabric_get_transcription_models
|
||||
set cmd (commandline -opc)[1]
|
||||
$cmd --list-transcription-models --shell-complete-list 2>/dev/null
|
||||
end
|
||||
|
||||
# Main completion function
|
||||
complete -c fabric -f
|
||||
function __fabric_register_completions
|
||||
set cmd $argv[1]
|
||||
complete -c $cmd -f
|
||||
|
||||
# Flag completions with arguments
|
||||
complete -c fabric -s p -l pattern -d "Choose a pattern from the available patterns" -a "(__fabric_get_patterns)"
|
||||
complete -c fabric -s v -l variable -d "Values for pattern variables, e.g. -v=#role:expert -v=#points:30"
|
||||
complete -c fabric -s C -l context -d "Choose a context from the available contexts" -a "(__fabric_get_contexts)"
|
||||
complete -c fabric -l session -d "Choose a session from the available sessions" -a "(__fabric_get_sessions)"
|
||||
complete -c fabric -s a -l attachment -d "Attachment path or URL (e.g. for OpenAI image recognition messages)" -r
|
||||
complete -c fabric -s t -l temperature -d "Set temperature (default: 0.7)"
|
||||
complete -c fabric -s T -l topp -d "Set top P (default: 0.9)"
|
||||
complete -c fabric -s P -l presencepenalty -d "Set presence penalty (default: 0.0)"
|
||||
complete -c fabric -s F -l frequencypenalty -d "Set frequency penalty (default: 0.0)"
|
||||
complete -c fabric -s m -l model -d "Choose model" -a "(__fabric_get_models)"
|
||||
complete -c fabric -l modelContextLength -d "Model context length (only affects ollama)"
|
||||
complete -c fabric -s o -l output -d "Output to file" -r
|
||||
complete -c fabric -s n -l latest -d "Number of latest patterns to list (default: 0)"
|
||||
complete -c fabric -s y -l youtube -d "YouTube video or play list URL to grab transcript, comments from it"
|
||||
complete -c fabric -s g -l language -d "Specify the Language Code for the chat, e.g. -g=en -g=zh"
|
||||
complete -c fabric -s u -l scrape_url -d "Scrape website URL to markdown using Jina AI"
|
||||
complete -c fabric -s q -l scrape_question -d "Search question using Jina AI"
|
||||
complete -c fabric -s e -l seed -d "Seed to be used for LMM generation"
|
||||
complete -c fabric -s w -l wipecontext -d "Wipe context" -a "(__fabric_get_contexts)"
|
||||
complete -c fabric -s W -l wipesession -d "Wipe session" -a "(__fabric_get_sessions)"
|
||||
complete -c fabric -l printcontext -d "Print context" -a "(__fabric_get_contexts)"
|
||||
complete -c fabric -l printsession -d "Print session" -a "(__fabric_get_sessions)"
|
||||
complete -c fabric -l address -d "The address to bind the REST API (default: :8080)"
|
||||
complete -c fabric -l api-key -d "API key used to secure server routes"
|
||||
complete -c fabric -l config -d "Path to YAML config file" -r -a "*.yaml *.yml"
|
||||
complete -c fabric -l search-location -d "Set location for web search results (e.g., 'America/Los_Angeles')"
|
||||
complete -c fabric -l image-file -d "Save generated image to specified file path (e.g., 'output.png')" -r -a "*.png *.webp *.jpeg *.jpg"
|
||||
complete -c fabric -l image-size -d "Image dimensions: 1024x1024, 1536x1024, 1024x1536, auto (default: auto)" -a "1024x1024 1536x1024 1024x1536 auto"
|
||||
complete -c fabric -l image-quality -d "Image quality: low, medium, high, auto (default: auto)" -a "low medium high auto"
|
||||
complete -c fabric -l image-compression -d "Compression level 0-100 for JPEG/WebP formats (default: not set)" -r
|
||||
complete -c fabric -l image-background -d "Background type: opaque, transparent (default: opaque, only for PNG/WebP)" -a "opaque transparent"
|
||||
complete -c fabric -l addextension -d "Register a new extension from config file path" -r -a "*.yaml *.yml"
|
||||
complete -c fabric -l rmextension -d "Remove a registered extension by name" -a "(__fabric_get_extensions)"
|
||||
complete -c fabric -l strategy -d "Choose a strategy from the available strategies" -a "(__fabric_get_strategies)"
|
||||
complete -c fabric -l think-start-tag -d "Start tag for thinking sections (default: <think>)"
|
||||
complete -c fabric -l think-end-tag -d "End tag for thinking sections (default: </think>)"
|
||||
# Flag completions with arguments
|
||||
complete -c $cmd -s p -l pattern -d "Choose a pattern from the available patterns" -a "(__fabric_get_patterns)"
|
||||
complete -c $cmd -s v -l variable -d "Values for pattern variables, e.g. -v=#role:expert -v=#points:30"
|
||||
complete -c $cmd -s C -l context -d "Choose a context from the available contexts" -a "(__fabric_get_contexts)"
|
||||
complete -c $cmd -l session -d "Choose a session from the available sessions" -a "(__fabric_get_sessions)"
|
||||
complete -c $cmd -s a -l attachment -d "Attachment path or URL (e.g. for OpenAI image recognition messages)" -r
|
||||
complete -c $cmd -s t -l temperature -d "Set temperature (default: 0.7)"
|
||||
complete -c $cmd -s T -l topp -d "Set top P (default: 0.9)"
|
||||
complete -c $cmd -s P -l presencepenalty -d "Set presence penalty (default: 0.0)"
|
||||
complete -c $cmd -s F -l frequencypenalty -d "Set frequency penalty (default: 0.0)"
|
||||
complete -c $cmd -s m -l model -d "Choose model" -a "(__fabric_get_models)"
|
||||
complete -c $cmd -s V -l vendor -d "Specify vendor for chosen model (e.g., -V \"LM Studio\" -m openai/gpt-oss-20b)" -a "(__fabric_get_vendors)"
|
||||
complete -c $cmd -l modelContextLength -d "Model context length (only affects ollama)"
|
||||
complete -c $cmd -s o -l output -d "Output to file" -r
|
||||
complete -c $cmd -s n -l latest -d "Number of latest patterns to list (default: 0)"
|
||||
complete -c $cmd -s y -l youtube -d "YouTube video or play list URL to grab transcript, comments from it"
|
||||
complete -c $cmd -s g -l language -d "Specify the Language Code for the chat, e.g. -g=en -g=zh"
|
||||
complete -c $cmd -s u -l scrape_url -d "Scrape website URL to markdown using Jina AI"
|
||||
complete -c $cmd -s q -l scrape_question -d "Search question using Jina AI"
|
||||
complete -c $cmd -s e -l seed -d "Seed to be used for LMM generation"
|
||||
complete -c $cmd -l thinking -d "Set reasoning/thinking level" -a "off low medium high"
|
||||
complete -c $cmd -s w -l wipecontext -d "Wipe context" -a "(__fabric_get_contexts)"
|
||||
complete -c $cmd -s W -l wipesession -d "Wipe session" -a "(__fabric_get_sessions)"
|
||||
complete -c $cmd -l printcontext -d "Print context" -a "(__fabric_get_contexts)"
|
||||
complete -c $cmd -l printsession -d "Print session" -a "(__fabric_get_sessions)"
|
||||
complete -c $cmd -l address -d "The address to bind the REST API (default: :8080)"
|
||||
complete -c $cmd -l api-key -d "API key used to secure server routes"
|
||||
complete -c $cmd -l config -d "Path to YAML config file" -r -a "*.yaml *.yml"
|
||||
complete -c $cmd -l search-location -d "Set location for web search results (e.g., 'America/Los_Angeles')"
|
||||
complete -c $cmd -l image-file -d "Save generated image to specified file path (e.g., 'output.png')" -r -a "*.png *.webp *.jpeg *.jpg"
|
||||
complete -c $cmd -l image-size -d "Image dimensions: 1024x1024, 1536x1024, 1024x1536, auto (default: auto)" -a "1024x1024 1536x1024 1024x1536 auto"
|
||||
complete -c $cmd -l image-quality -d "Image quality: low, medium, high, auto (default: auto)" -a "low medium high auto"
|
||||
complete -c $cmd -l image-compression -d "Compression level 0-100 for JPEG/WebP formats (default: not set)" -r
|
||||
complete -c $cmd -l image-background -d "Background type: opaque, transparent (default: opaque, only for PNG/WebP)" -a "opaque transparent"
|
||||
complete -c $cmd -l addextension -d "Register a new extension from config file path" -r -a "*.yaml *.yml"
|
||||
complete -c $cmd -l rmextension -d "Remove a registered extension by name" -a "(__fabric_get_extensions)"
|
||||
complete -c $cmd -l strategy -d "Choose a strategy from the available strategies" -a "(__fabric_get_strategies)"
|
||||
complete -c $cmd -l think-start-tag -d "Start tag for thinking sections (default: <think>)"
|
||||
complete -c $cmd -l think-end-tag -d "End tag for thinking sections (default: </think>)"
|
||||
complete -c $cmd -l voice -d "TTS voice name for supported models (e.g., Kore, Charon, Puck)" -a "(__fabric_get_gemini_voices)"
|
||||
complete -c $cmd -l transcribe-file -d "Audio or video file to transcribe" -r -a "*.mp3 *.mp4 *.mpeg *.mpga *.m4a *.wav *.webm"
|
||||
complete -c $cmd -l transcribe-model -d "Model to use for transcription (separate from chat model)" -a "(__fabric_get_transcription_models)"
|
||||
complete -c $cmd -l debug -d "Set debug level (0=off, 1=basic, 2=detailed, 3=trace)" -a "0 1 2 3"
|
||||
complete -c $cmd -l notification-command -d "Custom command to run for notifications (overrides built-in notifications)"
|
||||
|
||||
# Boolean flags (no arguments)
|
||||
complete -c fabric -s S -l setup -d "Run setup for all reconfigurable parts of fabric"
|
||||
complete -c fabric -s s -l stream -d "Stream"
|
||||
complete -c fabric -s r -l raw -d "Use the defaults of the model without sending chat options"
|
||||
complete -c fabric -s l -l listpatterns -d "List all patterns"
|
||||
complete -c fabric -s L -l listmodels -d "List all available models"
|
||||
complete -c fabric -s x -l listcontexts -d "List all contexts"
|
||||
complete -c fabric -s X -l listsessions -d "List all sessions"
|
||||
complete -c fabric -s U -l updatepatterns -d "Update patterns"
|
||||
complete -c fabric -s c -l copy -d "Copy to clipboard"
|
||||
complete -c fabric -l output-session -d "Output the entire session to the output file"
|
||||
complete -c fabric -s d -l changeDefaultModel -d "Change default model"
|
||||
complete -c fabric -l playlist -d "Prefer playlist over video if both ids are present in the URL"
|
||||
complete -c fabric -l transcript -d "Grab transcript from YouTube video and send to chat"
|
||||
complete -c fabric -l transcript-with-timestamps -d "Grab transcript from YouTube video with timestamps"
|
||||
complete -c fabric -l comments -d "Grab comments from YouTube video and send to chat"
|
||||
complete -c fabric -l metadata -d "Output video metadata"
|
||||
complete -c fabric -l readability -d "Convert HTML input into a clean, readable view"
|
||||
complete -c fabric -l input-has-vars -d "Apply variables to user input"
|
||||
complete -c fabric -l dry-run -d "Show what would be sent to the model without actually sending it"
|
||||
complete -c fabric -l search -d "Enable web search tool for supported models (Anthropic, OpenAI)"
|
||||
complete -c fabric -l serve -d "Serve the Fabric Rest API"
|
||||
complete -c fabric -l serveOllama -d "Serve the Fabric Rest API with ollama endpoints"
|
||||
complete -c fabric -l version -d "Print current version"
|
||||
complete -c fabric -l listextensions -d "List all registered extensions"
|
||||
complete -c fabric -l liststrategies -d "List all strategies"
|
||||
complete -c fabric -l listvendors -d "List all vendors"
|
||||
complete -c fabric -l shell-complete-list -d "Output raw list without headers/formatting (for shell completion)"
|
||||
complete -c fabric -l suppress-think -d "Suppress text enclosed in thinking tags"
|
||||
complete -c fabric -s h -l help -d "Show this help message"
|
||||
# Boolean flags (no arguments)
|
||||
complete -c $cmd -s S -l setup -d "Run setup for all reconfigurable parts of fabric"
|
||||
complete -c $cmd -s s -l stream -d "Stream"
|
||||
complete -c $cmd -s r -l raw -d "Use the defaults of the model without sending chat options"
|
||||
complete -c $cmd -s l -l listpatterns -d "List all patterns"
|
||||
complete -c $cmd -s L -l listmodels -d "List all available models"
|
||||
complete -c $cmd -s x -l listcontexts -d "List all contexts"
|
||||
complete -c $cmd -s X -l listsessions -d "List all sessions"
|
||||
complete -c $cmd -s U -l updatepatterns -d "Update patterns"
|
||||
complete -c $cmd -s c -l copy -d "Copy to clipboard"
|
||||
complete -c $cmd -l output-session -d "Output the entire session to the output file"
|
||||
complete -c $cmd -s d -l changeDefaultModel -d "Change default model"
|
||||
complete -c $cmd -l playlist -d "Prefer playlist over video if both ids are present in the URL"
|
||||
complete -c $cmd -l transcript -d "Grab transcript from YouTube video and send to chat"
|
||||
complete -c $cmd -l transcript-with-timestamps -d "Grab transcript from YouTube video with timestamps"
|
||||
complete -c $cmd -l comments -d "Grab comments from YouTube video and send to chat"
|
||||
complete -c $cmd -l metadata -d "Output video metadata"
|
||||
complete -c $cmd -l yt-dlp-args -d "Additional arguments to pass to yt-dlp (e.g. '--cookies-from-browser brave')"
|
||||
complete -c $cmd -l readability -d "Convert HTML input into a clean, readable view"
|
||||
complete -c $cmd -l input-has-vars -d "Apply variables to user input"
|
||||
complete -c $cmd -l no-variable-replacement -d "Disable pattern variable replacement"
|
||||
complete -c $cmd -l dry-run -d "Show what would be sent to the model without actually sending it"
|
||||
complete -c $cmd -l search -d "Enable web search tool for supported models (Anthropic, OpenAI, Gemini)"
|
||||
complete -c $cmd -l serve -d "Serve the Fabric Rest API"
|
||||
complete -c $cmd -l serveOllama -d "Serve the Fabric Rest API with ollama endpoints"
|
||||
complete -c $cmd -l version -d "Print current version"
|
||||
complete -c $cmd -l listextensions -d "List all registered extensions"
|
||||
complete -c $cmd -l liststrategies -d "List all strategies"
|
||||
complete -c $cmd -l listvendors -d "List all vendors"
|
||||
complete -c $cmd -l list-gemini-voices -d "List all available Gemini TTS voices"
|
||||
complete -c $cmd -l shell-complete-list -d "Output raw list without headers/formatting (for shell completion)"
|
||||
complete -c $cmd -l suppress-think -d "Suppress text enclosed in thinking tags"
|
||||
complete -c $cmd -l disable-responses-api -d "Disable OpenAI Responses API (default: false)"
|
||||
complete -c $cmd -l split-media-file -d "Split audio/video files larger than 25MB using ffmpeg"
|
||||
complete -c $cmd -l notification -d "Send desktop notification when command completes"
|
||||
complete -c $cmd -s h -l help -d "Show this help message"
|
||||
end
|
||||
|
||||
__fabric_register_completions fabric
|
||||
__fabric_register_completions fabric-ai
|
||||
|
||||
503
completions/setup-completions.sh
Executable file
503
completions/setup-completions.sh
Executable file
@@ -0,0 +1,503 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Fabric Shell Completions Setup Script
|
||||
# This script automatically installs shell completions for the fabric CLI
|
||||
# based on your current shell and the installed fabric command name.
|
||||
|
||||
set -e
|
||||
|
||||
# Global variables
|
||||
DRY_RUN=false
|
||||
# Base URL to fetch completion files when not available locally
|
||||
# Can be overridden via environment variable FABRIC_COMPLETIONS_BASE_URL
|
||||
FABRIC_COMPLETIONS_BASE_URL="${FABRIC_COMPLETIONS_BASE_URL:-https://raw.githubusercontent.com/danielmiessler/Fabric/refs/heads/main/completions}"
|
||||
TEMP_DIR=""
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored output
|
||||
print_info() {
|
||||
printf "${BLUE}[INFO]${NC} %s\n" "$1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
printf "${GREEN}[SUCCESS]${NC} %s\n" "$1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
printf "${YELLOW}[WARNING]${NC} %s\n" "$1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
printf "${RED}[ERROR]${NC} %s\n" "$1"
|
||||
}
|
||||
|
||||
print_dry_run() {
|
||||
printf "${CYAN}[DRY-RUN]${NC} %s\n" "$1"
|
||||
}
|
||||
|
||||
# Function to execute commands with dry-run support
|
||||
execute_command() {
|
||||
cmd="$1"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_dry_run "Would run: $cmd"
|
||||
return 0
|
||||
else
|
||||
eval "$cmd" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# Simple downloader that prefers curl, falls back to wget
|
||||
to_github_raw_url() {
|
||||
in_url="$1"
|
||||
case "$in_url" in
|
||||
https://github.com/*/*/blob/*)
|
||||
# Convert blob URL to raw
|
||||
# https://github.com/{owner}/{repo}/blob/{ref}/path -> https://raw.githubusercontent.com/{owner}/{repo}/{ref}/path
|
||||
echo "$in_url" | sed -E 's#https://github.com/([^/]+)/([^/]+)/blob/([^/]+)/#https://raw.githubusercontent.com/\1/\2/\3/#'
|
||||
;;
|
||||
https://github.com/*/*/tree/*)
|
||||
# Convert tree URL base + file path to raw
|
||||
# https://github.com/{owner}/{repo}/tree/{ref}/path -> https://raw.githubusercontent.com/{owner}/{repo}/{ref}/path
|
||||
echo "$in_url" | sed -E 's#https://github.com/([^/]+)/([^/]+)/tree/([^/]+)/#https://raw.githubusercontent.com/\1/\2/\3/#'
|
||||
;;
|
||||
*)
|
||||
echo "$in_url"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Simple downloader that prefers curl, falls back to wget
|
||||
download_file() {
|
||||
url="$1"
|
||||
dest="$2"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_dry_run "Would download: $url -> $dest"
|
||||
return 0
|
||||
fi
|
||||
|
||||
eff_url="$(to_github_raw_url "$url")"
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsSL "$eff_url" -o "$dest"
|
||||
return $?
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -q "$eff_url" -O "$dest"
|
||||
return $?
|
||||
else
|
||||
print_error "Neither 'curl' nor 'wget' is available to download: $url"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Attempt to obtain completion files. If local copies are missing,
|
||||
# download them into a temporary directory and return that directory path.
|
||||
obtain_completion_files() {
|
||||
obf_script_dir="$1"
|
||||
obf_need_download=false
|
||||
|
||||
if [ ! -f "$obf_script_dir/_fabric" ] || [ ! -f "$obf_script_dir/fabric.bash" ] || [ ! -f "$obf_script_dir/fabric.fish" ]; then
|
||||
obf_need_download=true
|
||||
fi
|
||||
|
||||
if [ "$obf_need_download" = false ]; then
|
||||
echo "$obf_script_dir"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Note: write only to stderr in this function except for the final echo which returns the path
|
||||
printf "%s\n" "[INFO] Local completion files not found; will download from GitHub." 1>&2
|
||||
printf "%s\n" "[INFO] Source: $FABRIC_COMPLETIONS_BASE_URL" 1>&2
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
printf "%s\n" "[DRY-RUN] Would create temporary directory for downloads" 1>&2
|
||||
echo "$obf_script_dir" # Keep using original for dry-run copies
|
||||
return 0
|
||||
fi
|
||||
|
||||
TEMP_DIR="$(mktemp -d 2>/dev/null || mktemp -d -t fabric-completions)"
|
||||
if [ ! -d "$TEMP_DIR" ]; then
|
||||
print_error "Failed to create temporary directory for downloads."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! download_file "$FABRIC_COMPLETIONS_BASE_URL/_fabric" "$TEMP_DIR/_fabric"; then
|
||||
print_error "Failed to download _fabric"
|
||||
return 1
|
||||
fi
|
||||
if [ ! -s "$TEMP_DIR/_fabric" ] || head -n1 "$TEMP_DIR/_fabric" | grep -qi "^<!DOCTYPE\|^<html"; then
|
||||
print_error "Downloaded _fabric appears invalid (empty or HTML). Check FABRIC_COMPLETIONS_BASE_URL."
|
||||
return 1
|
||||
fi
|
||||
if ! download_file "$FABRIC_COMPLETIONS_BASE_URL/fabric.bash" "$TEMP_DIR/fabric.bash"; then
|
||||
print_error "Failed to download fabric.bash"
|
||||
return 1
|
||||
fi
|
||||
if [ ! -s "$TEMP_DIR/fabric.bash" ] || head -n1 "$TEMP_DIR/fabric.bash" | grep -qi "^<!DOCTYPE\|^<html"; then
|
||||
print_error "Downloaded fabric.bash appears invalid (empty or HTML). Check FABRIC_COMPLETIONS_BASE_URL."
|
||||
return 1
|
||||
fi
|
||||
if ! download_file "$FABRIC_COMPLETIONS_BASE_URL/fabric.fish" "$TEMP_DIR/fabric.fish"; then
|
||||
print_error "Failed to download fabric.fish"
|
||||
return 1
|
||||
fi
|
||||
if [ ! -s "$TEMP_DIR/fabric.fish" ] || head -n1 "$TEMP_DIR/fabric.fish" | grep -qi "^<!DOCTYPE\|^<html"; then
|
||||
print_error "Downloaded fabric.fish appears invalid (empty or HTML). Check FABRIC_COMPLETIONS_BASE_URL."
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$TEMP_DIR"
|
||||
}
|
||||
|
||||
# Ensure directory exists, try sudo on permission failure
|
||||
ensure_dir() {
|
||||
dir="$1"
|
||||
# Expand ~ if present
|
||||
case "$dir" in
|
||||
~/*)
|
||||
dir="$HOME${dir#~}"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -d "$dir" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_dry_run "Would run: mkdir -p \"$dir\""
|
||||
print_dry_run "If permission denied, would run: sudo mkdir -p \"$dir\""
|
||||
return 0
|
||||
fi
|
||||
|
||||
if mkdir -p "$dir" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
if command -v sudo >/dev/null 2>&1 && sudo mkdir -p "$dir" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
print_error "Failed to create directory: $dir"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Copy file with sudo fallback on permission failure
|
||||
install_file() {
|
||||
src="$1"
|
||||
dest="$2"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_dry_run "Would run: cp \"$src\" \"$dest\""
|
||||
print_dry_run "If permission denied, would run: sudo cp \"$src\" \"$dest\""
|
||||
return 0
|
||||
fi
|
||||
|
||||
if cp "$src" "$dest" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
if command -v sudo >/dev/null 2>&1 && sudo cp "$src" "$dest" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
print_error "Failed to install file to: $dest"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Function to detect fabric command name
|
||||
detect_fabric_command() {
|
||||
if command -v fabric >/dev/null 2>&1; then
|
||||
echo "fabric"
|
||||
elif command -v fabric-ai >/dev/null 2>&1; then
|
||||
echo "fabric-ai"
|
||||
else
|
||||
print_error "Neither 'fabric' nor 'fabric-ai' command found in PATH"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to detect shell
|
||||
detect_shell() {
|
||||
if [ -n "$SHELL" ]; then
|
||||
basename "$SHELL"
|
||||
else
|
||||
print_warning "SHELL environment variable not set, defaulting to sh"
|
||||
echo "sh"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to get script directory
|
||||
get_script_dir() {
|
||||
# Get the directory where this script is located
|
||||
script_path="$(readlink -f "$0" 2>/dev/null || realpath "$0" 2>/dev/null || echo "$0")"
|
||||
dirname "$script_path"
|
||||
}
|
||||
|
||||
# Function to setup Zsh completions
|
||||
setup_zsh_completions() {
|
||||
fabric_cmd="$1"
|
||||
script_dir="$2"
|
||||
completion_file="_${fabric_cmd}"
|
||||
|
||||
print_info "Setting up Zsh completions for '$fabric_cmd'..."
|
||||
|
||||
# Try to use existing $fpath first, then fall back to default directories
|
||||
zsh_dirs=""
|
||||
|
||||
# Check if user's shell is zsh and try to get fpath from it
|
||||
if [ "$(basename "$SHELL")" = "zsh" ] && command -v zsh >/dev/null 2>&1; then
|
||||
# Get fpath from zsh by sourcing user's .zshrc first
|
||||
fpath_output=$(zsh -c "source \$HOME/.zshrc 2>/dev/null && print -l \$fpath" 2>/dev/null | head -5 | tr '\n' ' ')
|
||||
if [ -n "$fpath_output" ] && [ "$fpath_output" != "" ]; then
|
||||
print_info "Using directories from zsh \$fpath"
|
||||
zsh_dirs="$fpath_output"
|
||||
fi
|
||||
fi
|
||||
|
||||
# If we couldn't get fpath or it's empty, use default directories
|
||||
if [ -z "$zsh_dirs" ] || [ "$zsh_dirs" = "" ]; then
|
||||
print_info "Using default zsh completion directories"
|
||||
zsh_dirs="/usr/local/share/zsh/site-functions /opt/homebrew/share/zsh/site-functions /usr/share/zsh/site-functions ~/.local/share/zsh/site-functions"
|
||||
fi
|
||||
|
||||
installed=false
|
||||
|
||||
for dir in $zsh_dirs; do
|
||||
# Create directory (with sudo fallback if needed)
|
||||
if ensure_dir "$dir"; then
|
||||
if install_file "$script_dir/_fabric" "$dir/$completion_file"; then
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_success "Would install Zsh completion to: $dir/$completion_file"
|
||||
else
|
||||
print_success "Installed Zsh completion to: $dir/$completion_file"
|
||||
fi
|
||||
installed=true
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$installed" = false ]; then
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_warning "Would attempt to install Zsh completions but no writable directory found."
|
||||
else
|
||||
print_error "Failed to install Zsh completions. Try running with sudo or check permissions."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_info "Would suggest: Restart your shell or run 'autoload -U compinit && compinit' to enable completions."
|
||||
else
|
||||
print_info "Restart your shell or run 'autoload -U compinit && compinit' to enable completions."
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to setup Bash completions
|
||||
setup_bash_completions() {
|
||||
fabric_cmd="$1"
|
||||
script_dir="$2"
|
||||
completion_file="${fabric_cmd}.bash"
|
||||
|
||||
print_info "Setting up Bash completions for '$fabric_cmd'..."
|
||||
|
||||
# Try different completion directories
|
||||
bash_dirs="/etc/bash_completion.d /usr/local/etc/bash_completion.d /opt/homebrew/etc/bash_completion.d ~/.local/share/bash-completion/completions"
|
||||
installed=false
|
||||
|
||||
for dir in $bash_dirs; do
|
||||
if ensure_dir "$dir"; then
|
||||
if install_file "$script_dir/fabric.bash" "$dir/$completion_file"; then
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_success "Would install Bash completion to: $dir/$completion_file"
|
||||
else
|
||||
print_success "Installed Bash completion to: $dir/$completion_file"
|
||||
fi
|
||||
installed=true
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$installed" = false ]; then
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_warning "Would attempt to install Bash completions but no writable directory found."
|
||||
else
|
||||
print_error "Failed to install Bash completions. Try running with sudo or check permissions."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_info "Would suggest: Restart your shell or run 'source ~/.bashrc' to enable completions."
|
||||
else
|
||||
print_info "Restart your shell or run 'source ~/.bashrc' to enable completions."
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to setup Fish completions
|
||||
setup_fish_completions() {
|
||||
fabric_cmd="$1"
|
||||
script_dir="$2"
|
||||
completion_file="${fabric_cmd}.fish"
|
||||
|
||||
print_info "Setting up Fish completions for '$fabric_cmd'..."
|
||||
|
||||
# Fish completion directory
|
||||
fish_dir="$HOME/.config/fish/completions"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_dry_run "Would run: mkdir -p \"$fish_dir\""
|
||||
print_dry_run "Would run: cp \"$script_dir/fabric.fish\" \"$fish_dir/$completion_file\""
|
||||
print_success "Would install Fish completion to: $fish_dir/$completion_file"
|
||||
print_info "Fish will automatically load the completions (no restart needed)."
|
||||
elif mkdir -p "$fish_dir" 2>/dev/null; then
|
||||
if cp "$script_dir/fabric.fish" "$fish_dir/$completion_file"; then
|
||||
print_success "Installed Fish completion to: $fish_dir/$completion_file"
|
||||
print_info "Fish will automatically load the completions (no restart needed)."
|
||||
else
|
||||
print_error "Failed to copy Fish completion file."
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
print_error "Failed to create Fish completions directory: $fish_dir"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to setup completions for other shells
|
||||
setup_other_shell_completions() {
|
||||
fabric_cmd="$1"
|
||||
shell_name="$2"
|
||||
script_dir="$3"
|
||||
|
||||
print_warning "Shell '$shell_name' is not directly supported."
|
||||
print_info "You can manually source the completion files:"
|
||||
print_info " Bash-compatible: source $script_dir/fabric.bash"
|
||||
print_info " Zsh-compatible: source $script_dir/_fabric"
|
||||
}
|
||||
|
||||
# Function to show help
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Fabric Shell Completions Setup Script
|
||||
|
||||
USAGE:
|
||||
setup-completions.sh [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
--dry-run Show what commands would be run without executing them
|
||||
--help Show this help message
|
||||
|
||||
DESCRIPTION:
|
||||
This script automatically installs shell completions for the fabric CLI
|
||||
based on your current shell and the installed fabric command name.
|
||||
|
||||
The script will use completion files from the same directory as the script
|
||||
when available. If they are not present (e.g., when running via curl), it
|
||||
will download them from GitHub:
|
||||
|
||||
$FABRIC_COMPLETIONS_BASE_URL
|
||||
|
||||
You can override the download source by setting
|
||||
FABRIC_COMPLETIONS_BASE_URL to your preferred location.
|
||||
|
||||
Supports: zsh, bash, fish
|
||||
|
||||
The script will:
|
||||
1. Detect whether 'fabric' or 'fabric-ai' is installed
|
||||
2. Detect your current shell from the SHELL environment variable
|
||||
3. Install the appropriate completion file with the correct name
|
||||
4. Try multiple standard completion directories
|
||||
|
||||
EXAMPLES:
|
||||
./setup-completions.sh # Install completions
|
||||
./setup-completions.sh --dry-run # Show what would be done
|
||||
FABRIC_COMPLETIONS_BASE_URL="https://raw.githubusercontent.com/<owner>/<repo>/main/completions" \\
|
||||
./setup-completions.sh # Override download source
|
||||
./setup-completions.sh --help # Show this help
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Main function
|
||||
main() {
|
||||
# Parse command line arguments
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown option: $1"
|
||||
print_info "Use --help for usage information."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
print_info "Fabric Shell Completions Setup"
|
||||
print_info "==============================="
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_info "DRY RUN MODE - Commands will be shown but not executed"
|
||||
print_info ""
|
||||
fi
|
||||
|
||||
# Get script directory and obtain completion files (local or downloaded)
|
||||
script_dir="$(get_script_dir)"
|
||||
script_dir="$(obtain_completion_files "$script_dir" || echo "")"
|
||||
if [ -z "$script_dir" ]; then
|
||||
print_error "Unable to obtain completion files. Aborting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If we downloaded into a temp dir, arrange cleanup at process exit
|
||||
if [ -n "$TEMP_DIR" ] && [ -d "$TEMP_DIR" ]; then
|
||||
trap 'if [ -n "$TEMP_DIR" ] && [ -d "$TEMP_DIR" ]; then rm -rf "$TEMP_DIR"; fi' EXIT INT TERM
|
||||
fi
|
||||
|
||||
# Detect fabric command
|
||||
fabric_cmd="$(detect_fabric_command)"
|
||||
print_info "Detected fabric command: $fabric_cmd"
|
||||
|
||||
# Detect shell
|
||||
shell_name="$(detect_shell)"
|
||||
print_info "Detected shell: $shell_name"
|
||||
|
||||
# Setup completions based on shell
|
||||
case "$shell_name" in
|
||||
zsh)
|
||||
setup_zsh_completions "$fabric_cmd" "$script_dir"
|
||||
;;
|
||||
bash)
|
||||
setup_bash_completions "$fabric_cmd" "$script_dir"
|
||||
;;
|
||||
fish)
|
||||
setup_fish_completions "$fabric_cmd" "$script_dir"
|
||||
;;
|
||||
*)
|
||||
setup_other_shell_completions "$fabric_cmd" "$shell_name" "$script_dir"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
print_success "Dry-run completed! The above commands would set up shell completions."
|
||||
print_info "Run without --dry-run to actually install the completions."
|
||||
else
|
||||
print_success "Shell completion setup completed!"
|
||||
print_info "You can now use tab completion with the '$fabric_cmd' command."
|
||||
fi
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
26
data/patterns/create_story_about_person/system.md
Normal file
26
data/patterns/create_story_about_person/system.md
Normal file
@@ -0,0 +1,26 @@
|
||||
You are an expert creative writer specializing in character-driven narratives, and a keen observer of human psychology. Your task is to craft a compelling, realistic short story based on a psychological profile or personal data provided by the user.
|
||||
|
||||
**Input:**
|
||||
The user will provide a psychological profile or descriptive data about a fictional or real person. This input will be clearly delimited by triple backticks (```). It may include personality traits, habits, fears, motivations, strengths, weaknesses, background information, or specific behavioral patterns.
|
||||
|
||||
**Task Steps:**
|
||||
|
||||
1. **Analyze Profile:** Carefully read and internalize the provided psychological profile. Identify the core personality traits, typical reactions, strengths, and vulnerabilities of the individual.
|
||||
2. **Brainstorm Challenges:** Based on the analysis from Step 1, generate 3-5 common, relatable, everyday problems or minor dilemmas that a person with this specific profile might genuinely encounter. These challenges should be varied and could span social, professional, personal, or emotional domains.
|
||||
3. **Develop Strategies:** For each identified problem from Step 2, devise 1-2 specific, plausible methods or strategies that the character, consistent with their psychological profile, would naturally employ (or attempt to employ) to navigate, cope with, or solve these challenges. Consider both internal thought processes and external actions.
|
||||
4. **Construct Narrative:** Weave these problems and the character's responses into a cohesive, engaging short story (approximately 500-700 words, 3-5 paragraphs). The story should have a clear narrative flow, introducing the character, presenting the challenges, and showing their journey through them.
|
||||
5. **Maintain Consistency:** Throughout the story, ensure the character's actions, dialogue, internal monologue, and emotional reactions are consistently aligned with the psychological profile provided. The story should feel authentic to the character.
|
||||
|
||||
**Output Requirements:**
|
||||
|
||||
* **Format:** A continuous narrative short story.
|
||||
* **Tone:** Empathetic, realistic, and engaging.
|
||||
* **Content:** The story must clearly depict the character facing everyday problems and demonstrate their unique methods and strategies for navigating these challenges, directly reflecting the input profile.
|
||||
* **Length:** Approximately 500-700 words.
|
||||
* **Avoid:** Overly dramatic or fantastical scenarios unless the profile explicitly suggests such a context. Focus on the 'everyday common problems'.
|
||||
|
||||
**Example of Input Format:**
|
||||
|
||||
```
|
||||
[Psychological Profile/Data Here]
|
||||
```
|
||||
53
data/patterns/heal_person/system.md
Normal file
53
data/patterns/heal_person/system.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# IDENTITY and PURPOSE
|
||||
|
||||
You are an AI assistant whose primary responsibility is to interpret and analyze psychological profiles and/or psychology data files provided as input. Your role is to carefully process this data and use your expertise to develop a tailored plan aimed at spiritual and mental healing, as well as overall life improvement for the subject. You must approach each case with sensitivity, applying psychological knowledge and holistic strategies to create actionable, personalized recommendations that address both mental and spiritual well-being. Your focus is on structured, compassionate, and practical guidance that can help the individual make meaningful improvements in their life.
|
||||
|
||||
Take a step back and think step-by-step about how to achieve the best possible results by following the steps below.
|
||||
|
||||
# STEPS
|
||||
|
||||
- Carefully review the psychological-profile and/or psychology data file provided as input.
|
||||
|
||||
- Analyze the data to identify key issues, strengths, and areas needing improvement related to the subject's mental and spiritual well-being.
|
||||
|
||||
- Develop a comprehensive plan that includes specific strategies for spiritual healing, mental health improvement, and overall life enhancement.
|
||||
|
||||
- Structure your output to clearly outline recommendations, resources, and actionable steps tailored to the individual's unique profile.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
- Only output Markdown.
|
||||
|
||||
- Ensure your output is organized, clear, and easy to follow, using headings, subheadings, and bullet points where appropriate.
|
||||
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
# INPUT
|
||||
|
||||
INPUT:# IDENTITY and PURPOSE
|
||||
|
||||
You are an AI assistant whose primary responsibility is to interpret and analyze psychological profiles and/or psychology data files provided as input. Your role is to carefully process this data and use your expertise to develop a tailored plan aimed at spiritual and mental healing, as well as overall life improvement for the subject. You must approach each case with sensitivity, applying psychological knowledge and holistic strategies to create actionable, personalized recommendations that address both mental and spiritual well-being. Your focus is on structured, compassionate, and practical guidance that can help the individual make meaningful improvements in their life.
|
||||
|
||||
Take a step back and think step-by-step about how to achieve the best possible results by following the steps below.
|
||||
|
||||
# STEPS
|
||||
|
||||
- Carefully review the psychological-profile and/or psychology data file provided as input.
|
||||
|
||||
- Analyze the data to identify key issues, strengths, and areas needing improvement related to the subject's mental and spiritual well-being.
|
||||
|
||||
- Develop a comprehensive plan that includes specific strategies for spiritual healing, mental health improvement, and overall life enhancement.
|
||||
|
||||
- Structure your output to clearly outline recommendations, resources, and actionable steps tailored to the individual's unique profile.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
- Only output Markdown.
|
||||
|
||||
- Ensure your output is organized, clear, and easy to follow, using headings, subheadings, and bullet points where appropriate.
|
||||
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
# INPUT
|
||||
|
||||
INPUT:
|
||||
@@ -88,136 +88,137 @@
|
||||
84. **create_security_update**: Creates concise security updates for newsletters, covering stories, threats, advisories, vulnerabilities, and a summary of key issues.
|
||||
85. **create_show_intro**: Creates compelling short intros for podcasts, summarizing key topics and themes discussed in the episode.
|
||||
86. **create_sigma_rules**: Extracts Tactics, Techniques, and Procedures (TTPs) from security news and converts them into Sigma detection rules for host-based detections.
|
||||
87. **create_story_explanation**: Summarizes complex content in a clear, approachable story format that makes the concepts easy to understand.
|
||||
88. **create_stride_threat_model**: Create a STRIDE-based threat model for a system design, identifying assets, trust boundaries, data flows, and prioritizing threats with mitigations.
|
||||
89. **create_summary**: Summarizes content into a 20-word sentence, 10 main points (16 words max), and 5 key takeaways in Markdown format.
|
||||
90. **create_tags**: Identifies at least 5 tags from text content for mind mapping tools, including authors and existing tags if present.
|
||||
91. **create_threat_scenarios**: Identifies likely attack methods for any system by providing a narrative-based threat model, balancing risk and opportunity.
|
||||
92. **create_ttrc_graph**: Creates a CSV file showing the progress of Time to Remediate Critical Vulnerabilities over time using given data.
|
||||
93. **create_ttrc_narrative**: Creates a persuasive narrative highlighting progress in reducing the Time to Remediate Critical Vulnerabilities metric over time.
|
||||
94. **create_upgrade_pack**: Extracts world model and task algorithm updates from content, providing beliefs about how the world works and task performance.
|
||||
95. **create_user_story**: Writes concise and clear technical user stories for new features in complex software programs, formatted for all stakeholders.
|
||||
96. **create_video_chapters**: Extracts interesting topics and timestamps from a transcript, providing concise summaries of key moments.
|
||||
97. **create_visualization**: Transforms complex ideas into visualizations using intricate ASCII art, simplifying concepts where necessary.
|
||||
98. **dialog_with_socrates**: Engages in deep, meaningful dialogues to explore and challenge beliefs using the Socratic method.
|
||||
99. **enrich_blog_post**: Enhances Markdown blog files by applying instructions to improve structure, visuals, and readability for HTML rendering.
|
||||
100. **explain_code**: Explains code, security tool output, configuration text, and answers questions based on the provided input.
|
||||
101. **explain_docs**: Improves and restructures tool documentation into clear, concise instructions, including overviews, usage, use cases, and key features.
|
||||
102. **explain_math**: Helps you understand mathematical concepts in a clear and engaging way.
|
||||
103. **explain_project**: Summarizes project documentation into clear, concise sections covering the project, problem, solution, installation, usage, and examples.
|
||||
104. **explain_terms**: Produces a glossary of advanced terms from content, providing a definition, analogy, and explanation of why each term matters.
|
||||
105. **export_data_as_csv**: Extracts and outputs all data structures from the input in properly formatted CSV data.
|
||||
106. **extract_algorithm_update_recommendations**: Extracts concise, practical algorithm update recommendations from the input and outputs them in a bulleted list.
|
||||
107. **extract_article_wisdom**: Extracts surprising, insightful, and interesting information from content, categorizing it into sections like summary, ideas, quotes, facts, references, and recommendations.
|
||||
108. **extract_book_ideas**: Extracts and outputs 50 to 100 of the most surprising, insightful, and interesting ideas from a book's content.
|
||||
109. **extract_book_recommendations**: Extracts and outputs 50 to 100 practical, actionable recommendations from a book's content.
|
||||
110. **extract_business_ideas**: Extracts top business ideas from content and elaborates on the best 10 with unique differentiators.
|
||||
111. **extract_controversial_ideas**: Extracts and outputs controversial statements and supporting quotes from the input in a structured Markdown list.
|
||||
112. **extract_core_message**: Extracts and outputs a clear, concise sentence that articulates the core message of a given text or body of work.
|
||||
113. **extract_ctf_writeup**: Extracts a short writeup from a warstory-like text about a cyber security engagement.
|
||||
114. **extract_domains**: Extracts domains and URLs from content to identify sources used for articles, newsletters, and other publications.
|
||||
115. **extract_extraordinary_claims**: Extracts and outputs a list of extraordinary claims from conversations, focusing on scientifically disputed or false statements.
|
||||
116. **extract_ideas**: Extracts and outputs all the key ideas from input, presented as 15-word bullet points in Markdown.
|
||||
117. **extract_insights**: Extracts and outputs the most powerful and insightful ideas from text, formatted as 16-word bullet points in the INSIGHTS section, also IDEAS section.
|
||||
118. **extract_insights_dm**: Extracts and outputs all valuable insights and a concise summary of the content, including key points and topics discussed.
|
||||
119. **extract_instructions**: Extracts clear, actionable step-by-step instructions and main objectives from instructional video transcripts, organizing them into a concise list.
|
||||
120. **extract_jokes**: Extracts jokes from text content, presenting each joke with its punchline in separate bullet points.
|
||||
121. **extract_latest_video**: Extracts the latest video URL from a YouTube RSS feed and outputs the URL only.
|
||||
122. **extract_main_activities**: Extracts key events and activities from transcripts or logs, providing a summary of what happened.
|
||||
123. **extract_main_idea**: Extracts the main idea and key recommendation from the input, summarizing them in 15-word sentences.
|
||||
124. **extract_most_redeeming_thing**: Extracts the most redeeming aspect from an input, summarizing it in a single 15-word sentence.
|
||||
125. **extract_patterns**: Extracts and analyzes recurring, surprising, and insightful patterns from input, providing detailed analysis and advice for builders.
|
||||
126. **extract_poc**: Extracts proof of concept URLs and validation methods from security reports, providing the URL and command to run.
|
||||
127. **extract_predictions**: Extracts predictions from input, including specific details such as date, confidence level, and verification method.
|
||||
128. **extract_primary_problem**: Extracts the primary problem with the world as presented in a given text or body of work.
|
||||
129. **extract_primary_solution**: Extracts the primary solution for the world as presented in a given text or body of work.
|
||||
130. **extract_product_features**: Extracts and outputs a list of product features from the provided input in a bulleted format.
|
||||
131. **extract_questions**: Extracts and outputs all questions asked by the interviewer in a conversation or interview.
|
||||
132. **extract_recipe**: Extracts and outputs a recipe with a short meal description, ingredients with measurements, and preparation steps.
|
||||
133. **extract_recommendations**: Extracts and outputs concise, practical recommendations from a given piece of content in a bulleted list.
|
||||
134. **extract_references**: Extracts and outputs a bulleted list of references to art, stories, books, literature, and other sources from content.
|
||||
135. **extract_skills**: Extracts and classifies skills from a job description into a table, separating each skill and classifying it as either hard or soft.
|
||||
136. **extract_song_meaning**: Analyzes a song to provide a summary of its meaning, supported by detailed evidence from lyrics, artist commentary, and fan analysis.
|
||||
137. **extract_sponsors**: Extracts and lists official sponsors and potential sponsors from a provided transcript.
|
||||
138. **extract_videoid**: Extracts and outputs the video ID from any given URL.
|
||||
139. **extract_wisdom**: Extracts surprising, insightful, and interesting information from text on topics like human flourishing, AI, learning, and more.
|
||||
140. **extract_wisdom_agents**: Extracts valuable insights, ideas, quotes, and references from content, emphasizing topics like human flourishing, AI, learning, and technology.
|
||||
141. **extract_wisdom_dm**: Extracts all valuable, insightful, and thought-provoking information from content, focusing on topics like human flourishing, AI, learning, and technology.
|
||||
142. **extract_wisdom_nometa**: Extracts insights, ideas, quotes, habits, facts, references, and recommendations from content, focusing on human flourishing, AI, technology, and related topics.
|
||||
143. **find_female_life_partner**: Analyzes criteria for finding a female life partner and provides clear, direct, and poetic descriptions.
|
||||
144. **find_hidden_message**: Extracts overt and hidden political messages, justifications, audience actions, and a cynical analysis from content.
|
||||
145. **find_logical_fallacies**: Identifies and analyzes fallacies in arguments, classifying them as formal or informal with detailed reasoning.
|
||||
146. **get_wow_per_minute**: Determines the wow-factor of content per minute based on surprise, novelty, insight, value, and wisdom, measuring how rewarding the content is for the viewer.
|
||||
147. **get_youtube_rss**: Returns the RSS URL for a given YouTube channel based on the channel ID or URL.
|
||||
148. **humanize**: Rewrites AI-generated text to sound natural, conversational, and easy to understand, maintaining clarity and simplicity.
|
||||
149. **identify_dsrp_distinctions**: Encourages creative, systems-based thinking by exploring distinctions, boundaries, and their implications, drawing on insights from prominent systems thinkers.
|
||||
150. **identify_dsrp_perspectives**: Explores the concept of distinctions in systems thinking, focusing on how boundaries define ideas, influence understanding, and reveal or obscure insights.
|
||||
151. **identify_dsrp_relationships**: Encourages exploration of connections, distinctions, and boundaries between ideas, inspired by systems thinkers to reveal new insights and patterns in complex systems.
|
||||
152. **identify_dsrp_systems**: Encourages organizing ideas into systems of parts and wholes, inspired by systems thinkers to explore relationships and how changes in organization impact meaning and understanding.
|
||||
153. **identify_job_stories**: Identifies key job stories or requirements for roles.
|
||||
154. **improve_academic_writing**: Refines text into clear, concise academic language while improving grammar, coherence, and clarity, with a list of changes.
|
||||
155. **improve_prompt**: Improves an LLM/AI prompt by applying expert prompt writing strategies for better results and clarity.
|
||||
156. **improve_report_finding**: Improves a penetration test security finding by providing detailed descriptions, risks, recommendations, references, quotes, and a concise summary in markdown format.
|
||||
157. **improve_writing**: Refines text by correcting grammar, enhancing style, improving clarity, and maintaining the original meaning. skills.
|
||||
158. **judge_output**: Evaluates Honeycomb queries by judging their effectiveness, providing critiques and outcomes based on language nuances and analytics relevance.
|
||||
159. **label_and_rate**: Labels content with up to 20 single-word tags and rates it based on idea count and relevance to human meaning, AI, and other related themes, assigning a tier (S, A, B, C, D) and a quality score.
|
||||
160. **md_callout**: Classifies content and generates a markdown callout based on the provided text, selecting the most appropriate type.
|
||||
161. **official_pattern_template**: Template to use if you want to create new fabric patterns.
|
||||
162. **prepare_7s_strategy**: Prepares a comprehensive briefing document from 7S's strategy capturing organizational profile, strategic elements, and market dynamics with clear, concise, and organized content.
|
||||
163. **provide_guidance**: Provides psychological and life coaching advice, including analysis, recommendations, and potential diagnoses, with a compassionate and honest tone.
|
||||
164. **rate_ai_response**: Rates the quality of AI responses by comparing them to top human expert performance, assigning a letter grade, reasoning, and providing a 1-100 score based on the evaluation.
|
||||
165. **rate_ai_result**: Assesses the quality of AI/ML/LLM work by deeply analyzing content, instructions, and output, then rates performance based on multiple dimensions, including coverage, creativity, and interdisciplinary thinking.
|
||||
166. **rate_content**: Labels content with up to 20 single-word tags and rates it based on idea count and relevance to human meaning, AI, and other related themes, assigning a tier (S, A, B, C, D) and a quality score.
|
||||
167. **rate_value**: Produces the best possible output by deeply analyzing and understanding the input and its intended purpose.
|
||||
168. **raw_query**: Fully digests and contemplates the input to produce the best possible result based on understanding the sender's intent.
|
||||
169. **recommend_artists**: Recommends a personalized festival schedule with artists aligned to your favorite styles and interests, including rationale.
|
||||
170. **recommend_pipeline_upgrades**: Optimizes vulnerability-checking pipelines by incorporating new information and improving their efficiency, with detailed explanations of changes.
|
||||
171. **recommend_talkpanel_topics**: Produces a clean set of proposed talks or panel talking points for a person based on their interests and goals, formatted for submission to a conference organizer.
|
||||
172. **refine_design_document**: Refines a design document based on a design review by analyzing, mapping concepts, and implementing changes using valid Markdown.
|
||||
173. **review_design**: Reviews and analyzes architecture design, focusing on clarity, component design, system integrations, security, performance, scalability, and data management.
|
||||
174. **sanitize_broken_html_to_markdown**: Converts messy HTML into clean, properly formatted Markdown, applying custom styling and ensuring compatibility with Vite.
|
||||
175. **show_fabric_options_markmap**: Visualizes the functionality of the Fabric framework by representing its components, commands, and features based on the provided input.
|
||||
176. **solve_with_cot**: Provides detailed, step-by-step responses with chain of thought reasoning, using structured thinking, reflection, and output sections.
|
||||
177. **suggest_pattern**: Suggests appropriate fabric patterns or commands based on user input, providing clear explanations and options for users.
|
||||
178. **summarize**: Summarizes content into a 20-word sentence, main points, and takeaways, formatted with numbered lists in Markdown.
|
||||
179. **summarize_board_meeting**: Creates formal meeting notes from board meeting transcripts for corporate governance documentation.
|
||||
180. **summarize_debate**: Summarizes debates, identifies primary disagreement, extracts arguments, and provides analysis of evidence and argument strength to predict outcomes.
|
||||
181. **summarize_git_changes**: Summarizes recent project updates from the last 7 days, focusing on key changes with enthusiasm.
|
||||
182. **summarize_git_diff**: Summarizes and organizes Git diff changes with clear, succinct commit messages and bullet points.
|
||||
183. **summarize_lecture**: Extracts relevant topics, definitions, and tools from lecture transcripts, providing structured summaries with timestamps and key takeaways.
|
||||
184. **summarize_legislation**: Summarizes complex political proposals and legislation by analyzing key points, proposed changes, and providing balanced, positive, and cynical characterizations.
|
||||
185. **summarize_meeting**: Analyzes meeting transcripts to extract a structured summary, including an overview, key points, tasks, decisions, challenges, timeline, references, and next steps.
|
||||
186. **summarize_micro**: Summarizes content into a 20-word sentence, 3 main points, and 3 takeaways, formatted in clear, concise Markdown.
|
||||
187. **summarize_newsletter**: Extracts the most meaningful, interesting, and useful content from a newsletter, summarizing key sections such as content, opinions, tools, companies, and follow-up items in clear, structured Markdown.
|
||||
188. **summarize_paper**: Summarizes an academic paper by detailing its title, authors, technical approach, distinctive features, experimental setup, results, advantages, limitations, and conclusion in a clear, structured format using human-readable Markdown.
|
||||
189. **summarize_prompt**: Summarizes AI chat prompts by describing the primary function, unique approach, and expected output in a concise paragraph. The summary is focused on the prompt's purpose without unnecessary details or formatting.
|
||||
190. **summarize_pull-requests**: Summarizes pull requests for a coding project by providing a summary and listing the top PRs with human-readable descriptions.
|
||||
191. **summarize_rpg_session**: Summarizes a role-playing game session by extracting key events, combat stats, character changes, quotes, and more.
|
||||
192. **t_analyze_challenge_handling**: Provides 8-16 word bullet points evaluating how well challenges are being addressed, calling out any lack of effort.
|
||||
193. **t_check_metrics**: Analyzes deep context from the TELOS file and input instruction, then provides a wisdom-based output while considering metrics and KPIs to assess recent improvements.
|
||||
194. **t_create_h3_career**: Summarizes context and produces wisdom-based output by deeply analyzing both the TELOS File and the input instruction, considering the relationship between the two.
|
||||
195. **t_create_opening_sentences**: Describes from TELOS file the person's identity, goals, and actions in 4 concise, 32-word bullet points, humbly.
|
||||
196. **t_describe_life_outlook**: Describes from TELOS file a person's life outlook in 5 concise, 16-word bullet points.
|
||||
197. **t_extract_intro_sentences**: Summarizes from TELOS file a person's identity, work, and current projects in 5 concise and grounded bullet points.
|
||||
198. **t_extract_panel_topics**: Creates 5 panel ideas with titles and descriptions based on deep context from a TELOS file and input.
|
||||
199. **t_find_blindspots**: Identify potential blindspots in thinking, frames, or models that may expose the individual to error or risk.
|
||||
200. **t_find_negative_thinking**: Analyze a TELOS file and input to identify negative thinking in documents or journals, followed by tough love encouragement.
|
||||
201. **t_find_neglected_goals**: Analyze a TELOS file and input instructions to identify goals or projects that have not been worked on recently.
|
||||
202. **t_give_encouragement**: Analyze a TELOS file and input instructions to evaluate progress, provide encouragement, and offer recommendations for continued effort.
|
||||
203. **t_red_team_thinking**: Analyze a TELOS file and input instructions to red-team thinking, models, and frames, then provide recommendations for improvement.
|
||||
204. **t_threat_model_plans**: Analyze a TELOS file and input instructions to create threat models for a life plan and recommend improvements.
|
||||
205. **t_visualize_mission_goals_projects**: Analyze a TELOS file and input instructions to create an ASCII art diagram illustrating the relationship of missions, goals, and projects.
|
||||
206. **t_year_in_review**: Analyze a TELOS file to create insights about a person or entity, then summarize accomplishments and visualizations in bullet points.
|
||||
207. **to_flashcards**: Create Anki flashcards from a given text, focusing on concise, optimized questions and answers without external context.
|
||||
208. **transcribe_minutes**: Extracts (from meeting transcription) meeting minutes, identifying actionables, insightful ideas, decisions, challenges, and next steps in a structured format.
|
||||
209. **translate**: Translates sentences or documentation into the specified language code while maintaining the original formatting and tone.
|
||||
210. **tweet**: Provides a step-by-step guide on crafting engaging tweets with emojis, covering Twitter basics, account creation, features, and audience targeting.
|
||||
211. **write_essay**: Writes essays in the style of a specified author, embodying their unique voice, vocabulary, and approach. Uses `author_name` variable.
|
||||
212. **write_essay_pg**: Writes concise, clear essays in the style of Paul Graham, focusing on simplicity, clarity, and illumination of the provided topic.
|
||||
213. **write_hackerone_report**: Generates concise, clear, and reproducible bug bounty reports, detailing vulnerability impact, steps to reproduce, and exploit details for triagers.
|
||||
214. **write_latex**: Generates syntactically correct LaTeX code for a new.tex document, ensuring proper formatting and compatibility with pdflatex.
|
||||
215. **write_micro_essay**: Writes concise, clear, and illuminating essays on the given topic in the style of Paul Graham.
|
||||
216. **write_nuclei_template_rule**: Generates Nuclei YAML templates for detecting vulnerabilities using HTTP requests, matchers, extractors, and dynamic data extraction.
|
||||
217. **write_pull-request**: Drafts detailed pull request descriptions, explaining changes, providing reasoning, and identifying potential bugs from the git diff command output.
|
||||
218. **write_semgrep_rule**: Creates accurate and working Semgrep rules based on input, following syntax guidelines and specific language considerations.
|
||||
219. **youtube_summary**: Create concise, timestamped Youtube video summaries that highlight key points.
|
||||
87. **create_story_about_person**: Creates compelling, realistic short stories based on psychological profiles, showing how characters navigate everyday problems using strategies consistent with their personality traits.
|
||||
88. **create_story_explanation**: Summarizes complex content in a clear, approachable story format that makes the concepts easy to understand.
|
||||
89. **create_stride_threat_model**: Create a STRIDE-based threat model for a system design, identifying assets, trust boundaries, data flows, and prioritizing threats with mitigations.
|
||||
90. **create_summary**: Summarizes content into a 20-word sentence, 10 main points (16 words max), and 5 key takeaways in Markdown format.
|
||||
91. **create_tags**: Identifies at least 5 tags from text content for mind mapping tools, including authors and existing tags if present.
|
||||
92. **create_threat_scenarios**: Identifies likely attack methods for any system by providing a narrative-based threat model, balancing risk and opportunity.
|
||||
93. **create_ttrc_graph**: Creates a CSV file showing the progress of Time to Remediate Critical Vulnerabilities over time using given data.
|
||||
94. **create_ttrc_narrative**: Creates a persuasive narrative highlighting progress in reducing the Time to Remediate Critical Vulnerabilities metric over time.
|
||||
95. **create_upgrade_pack**: Extracts world model and task algorithm updates from content, providing beliefs about how the world works and task performance.
|
||||
96. **create_user_story**: Writes concise and clear technical user stories for new features in complex software programs, formatted for all stakeholders.
|
||||
97. **create_video_chapters**: Extracts interesting topics and timestamps from a transcript, providing concise summaries of key moments.
|
||||
98. **create_visualization**: Transforms complex ideas into visualizations using intricate ASCII art, simplifying concepts where necessary.
|
||||
99. **dialog_with_socrates**: Engages in deep, meaningful dialogues to explore and challenge beliefs using the Socratic method.
|
||||
100. **enrich_blog_post**: Enhances Markdown blog files by applying instructions to improve structure, visuals, and readability for HTML rendering.
|
||||
101. **explain_code**: Explains code, security tool output, configuration text, and answers questions based on the provided input.
|
||||
102. **explain_docs**: Improves and restructures tool documentation into clear, concise instructions, including overviews, usage, use cases, and key features.
|
||||
103. **explain_math**: Helps you understand mathematical concepts in a clear and engaging way.
|
||||
104. **explain_project**: Summarizes project documentation into clear, concise sections covering the project, problem, solution, installation, usage, and examples.
|
||||
105. **explain_terms**: Produces a glossary of advanced terms from content, providing a definition, analogy, and explanation of why each term matters.
|
||||
106. **export_data_as_csv**: Extracts and outputs all data structures from the input in properly formatted CSV data.
|
||||
107. **extract_algorithm_update_recommendations**: Extracts concise, practical algorithm update recommendations from the input and outputs them in a bulleted list.
|
||||
108. **extract_article_wisdom**: Extracts surprising, insightful, and interesting information from content, categorizing it into sections like summary, ideas, quotes, facts, references, and recommendations.
|
||||
109. **extract_book_ideas**: Extracts and outputs 50 to 100 of the most surprising, insightful, and interesting ideas from a book's content.
|
||||
110. **extract_book_recommendations**: Extracts and outputs 50 to 100 practical, actionable recommendations from a book's content.
|
||||
111. **extract_business_ideas**: Extracts top business ideas from content and elaborates on the best 10 with unique differentiators.
|
||||
112. **extract_controversial_ideas**: Extracts and outputs controversial statements and supporting quotes from the input in a structured Markdown list.
|
||||
113. **extract_core_message**: Extracts and outputs a clear, concise sentence that articulates the core message of a given text or body of work.
|
||||
114. **extract_ctf_writeup**: Extracts a short writeup from a warstory-like text about a cyber security engagement.
|
||||
115. **extract_domains**: Extracts domains and URLs from content to identify sources used for articles, newsletters, and other publications.
|
||||
116. **extract_extraordinary_claims**: Extracts and outputs a list of extraordinary claims from conversations, focusing on scientifically disputed or false statements.
|
||||
117. **extract_ideas**: Extracts and outputs all the key ideas from input, presented as 15-word bullet points in Markdown.
|
||||
118. **extract_insights**: Extracts and outputs the most powerful and insightful ideas from text, formatted as 16-word bullet points in the INSIGHTS section, also IDEAS section.
|
||||
119. **extract_insights_dm**: Extracts and outputs all valuable insights and a concise summary of the content, including key points and topics discussed.
|
||||
120. **extract_instructions**: Extracts clear, actionable step-by-step instructions and main objectives from instructional video transcripts, organizing them into a concise list.
|
||||
121. **extract_jokes**: Extracts jokes from text content, presenting each joke with its punchline in separate bullet points.
|
||||
122. **extract_latest_video**: Extracts the latest video URL from a YouTube RSS feed and outputs the URL only.
|
||||
123. **extract_main_activities**: Extracts key events and activities from transcripts or logs, providing a summary of what happened.
|
||||
124. **extract_main_idea**: Extracts the main idea and key recommendation from the input, summarizing them in 15-word sentences.
|
||||
125. **extract_most_redeeming_thing**: Extracts the most redeeming aspect from an input, summarizing it in a single 15-word sentence.
|
||||
126. **extract_patterns**: Extracts and analyzes recurring, surprising, and insightful patterns from input, providing detailed analysis and advice for builders.
|
||||
127. **extract_poc**: Extracts proof of concept URLs and validation methods from security reports, providing the URL and command to run.
|
||||
128. **extract_predictions**: Extracts predictions from input, including specific details such as date, confidence level, and verification method.
|
||||
129. **extract_primary_problem**: Extracts the primary problem with the world as presented in a given text or body of work.
|
||||
130. **extract_primary_solution**: Extracts the primary solution for the world as presented in a given text or body of work.
|
||||
131. **extract_product_features**: Extracts and outputs a list of product features from the provided input in a bulleted format.
|
||||
132. **extract_questions**: Extracts and outputs all questions asked by the interviewer in a conversation or interview.
|
||||
133. **extract_recipe**: Extracts and outputs a recipe with a short meal description, ingredients with measurements, and preparation steps.
|
||||
134. **extract_recommendations**: Extracts and outputs concise, practical recommendations from a given piece of content in a bulleted list.
|
||||
135. **extract_references**: Extracts and outputs a bulleted list of references to art, stories, books, literature, and other sources from content.
|
||||
136. **extract_skills**: Extracts and classifies skills from a job description into a table, separating each skill and classifying it as either hard or soft.
|
||||
137. **extract_song_meaning**: Analyzes a song to provide a summary of its meaning, supported by detailed evidence from lyrics, artist commentary, and fan analysis.
|
||||
138. **extract_sponsors**: Extracts and lists official sponsors and potential sponsors from a provided transcript.
|
||||
139. **extract_videoid**: Extracts and outputs the video ID from any given URL.
|
||||
140. **extract_wisdom**: Extracts surprising, insightful, and interesting information from text on topics like human flourishing, AI, learning, and more.
|
||||
141. **extract_wisdom_agents**: Extracts valuable insights, ideas, quotes, and references from content, emphasizing topics like human flourishing, AI, learning, and technology.
|
||||
142. **extract_wisdom_dm**: Extracts all valuable, insightful, and thought-provoking information from content, focusing on topics like human flourishing, AI, learning, and technology.
|
||||
143. **extract_wisdom_nometa**: Extracts insights, ideas, quotes, habits, facts, references, and recommendations from content, focusing on human flourishing, AI, technology, and related topics.
|
||||
144. **find_female_life_partner**: Analyzes criteria for finding a female life partner and provides clear, direct, and poetic descriptions.
|
||||
145. **find_hidden_message**: Extracts overt and hidden political messages, justifications, audience actions, and a cynical analysis from content.
|
||||
146. **find_logical_fallacies**: Identifies and analyzes fallacies in arguments, classifying them as formal or informal with detailed reasoning.
|
||||
147. **get_wow_per_minute**: Determines the wow-factor of content per minute based on surprise, novelty, insight, value, and wisdom, measuring how rewarding the content is for the viewer.
|
||||
148. **get_youtube_rss**: Returns the RSS URL for a given YouTube channel based on the channel ID or URL.
|
||||
149. **heal_person**: Develops a comprehensive plan for spiritual and mental healing based on psychological profiles, providing personalized recommendations for mental health improvement and overall life enhancement.
|
||||
150. **humanize**: Rewrites AI-generated text to sound natural, conversational, and easy to understand, maintaining clarity and simplicity.
|
||||
151. **identify_dsrp_distinctions**: Encourages creative, systems-based thinking by exploring distinctions, boundaries, and their implications, drawing on insights from prominent systems thinkers.
|
||||
152. **identify_dsrp_perspectives**: Explores the concept of distinctions in systems thinking, focusing on how boundaries define ideas, influence understanding, and reveal or obscure insights.
|
||||
153. **identify_dsrp_relationships**: Encourages exploration of connections, distinctions, and boundaries between ideas, inspired by systems thinkers to reveal new insights and patterns in complex systems.
|
||||
154. **identify_dsrp_systems**: Encourages organizing ideas into systems of parts and wholes, inspired by systems thinkers to explore relationships and how changes in organization impact meaning and understanding.
|
||||
155. **identify_job_stories**: Identifies key job stories or requirements for roles.
|
||||
156. **improve_academic_writing**: Refines text into clear, concise academic language while improving grammar, coherence, and clarity, with a list of changes.
|
||||
157. **improve_prompt**: Improves an LLM/AI prompt by applying expert prompt writing strategies for better results and clarity.
|
||||
158. **improve_report_finding**: Improves a penetration test security finding by providing detailed descriptions, risks, recommendations, references, quotes, and a concise summary in markdown format.
|
||||
159. **improve_writing**: Refines text by correcting grammar, enhancing style, improving clarity, and maintaining the original meaning. skills.
|
||||
160. **judge_output**: Evaluates Honeycomb queries by judging their effectiveness, providing critiques and outcomes based on language nuances and analytics relevance.
|
||||
161. **label_and_rate**: Labels content with up to 20 single-word tags and rates it based on idea count and relevance to human meaning, AI, and other related themes, assigning a tier (S, A, B, C, D) and a quality score.
|
||||
162. **md_callout**: Classifies content and generates a markdown callout based on the provided text, selecting the most appropriate type.
|
||||
163. **official_pattern_template**: Template to use if you want to create new fabric patterns.
|
||||
164. **prepare_7s_strategy**: Prepares a comprehensive briefing document from 7S's strategy capturing organizational profile, strategic elements, and market dynamics with clear, concise, and organized content.
|
||||
165. **provide_guidance**: Provides psychological and life coaching advice, including analysis, recommendations, and potential diagnoses, with a compassionate and honest tone.
|
||||
166. **rate_ai_response**: Rates the quality of AI responses by comparing them to top human expert performance, assigning a letter grade, reasoning, and providing a 1-100 score based on the evaluation.
|
||||
167. **rate_ai_result**: Assesses the quality of AI/ML/LLM work by deeply analyzing content, instructions, and output, then rates performance based on multiple dimensions, including coverage, creativity, and interdisciplinary thinking.
|
||||
168. **rate_content**: Labels content with up to 20 single-word tags and rates it based on idea count and relevance to human meaning, AI, and other related themes, assigning a tier (S, A, B, C, D) and a quality score.
|
||||
169. **rate_value**: Produces the best possible output by deeply analyzing and understanding the input and its intended purpose.
|
||||
170. **raw_query**: Fully digests and contemplates the input to produce the best possible result based on understanding the sender's intent.
|
||||
171. **recommend_artists**: Recommends a personalized festival schedule with artists aligned to your favorite styles and interests, including rationale.
|
||||
172. **recommend_pipeline_upgrades**: Optimizes vulnerability-checking pipelines by incorporating new information and improving their efficiency, with detailed explanations of changes.
|
||||
173. **recommend_talkpanel_topics**: Produces a clean set of proposed talks or panel talking points for a person based on their interests and goals, formatted for submission to a conference organizer.
|
||||
174. **refine_design_document**: Refines a design document based on a design review by analyzing, mapping concepts, and implementing changes using valid Markdown.
|
||||
175. **review_design**: Reviews and analyzes architecture design, focusing on clarity, component design, system integrations, security, performance, scalability, and data management.
|
||||
176. **sanitize_broken_html_to_markdown**: Converts messy HTML into clean, properly formatted Markdown, applying custom styling and ensuring compatibility with Vite.
|
||||
177. **solve_with_cot**: Provides detailed, step-by-step responses with chain of thought reasoning, using structured thinking, reflection, and output sections.
|
||||
178. **suggest_pattern**: Suggests appropriate fabric patterns or commands based on user input, providing clear explanations and options for users.
|
||||
179. **summarize**: Summarizes content into a 20-word sentence, main points, and takeaways, formatted with numbered lists in Markdown.
|
||||
180. **summarize_board_meeting**: Creates formal meeting notes from board meeting transcripts for corporate governance documentation.
|
||||
181. **summarize_debate**: Summarizes debates, identifies primary disagreement, extracts arguments, and provides analysis of evidence and argument strength to predict outcomes.
|
||||
182. **summarize_git_changes**: Summarizes recent project updates from the last 7 days, focusing on key changes with enthusiasm.
|
||||
183. **summarize_git_diff**: Summarizes and organizes Git diff changes with clear, succinct commit messages and bullet points.
|
||||
184. **summarize_lecture**: Extracts relevant topics, definitions, and tools from lecture transcripts, providing structured summaries with timestamps and key takeaways.
|
||||
185. **summarize_legislation**: Summarizes complex political proposals and legislation by analyzing key points, proposed changes, and providing balanced, positive, and cynical characterizations.
|
||||
186. **summarize_meeting**: Analyzes meeting transcripts to extract a structured summary, including an overview, key points, tasks, decisions, challenges, timeline, references, and next steps.
|
||||
187. **summarize_micro**: Summarizes content into a 20-word sentence, 3 main points, and 3 takeaways, formatted in clear, concise Markdown.
|
||||
188. **summarize_newsletter**: Extracts the most meaningful, interesting, and useful content from a newsletter, summarizing key sections such as content, opinions, tools, companies, and follow-up items in clear, structured Markdown.
|
||||
189. **summarize_paper**: Summarizes an academic paper by detailing its title, authors, technical approach, distinctive features, experimental setup, results, advantages, limitations, and conclusion in a clear, structured format using human-readable Markdown.
|
||||
190. **summarize_prompt**: Summarizes AI chat prompts by describing the primary function, unique approach, and expected output in a concise paragraph. The summary is focused on the prompt's purpose without unnecessary details or formatting.
|
||||
191. **summarize_pull-requests**: Summarizes pull requests for a coding project by providing a summary and listing the top PRs with human-readable descriptions.
|
||||
192. **summarize_rpg_session**: Summarizes a role-playing game session by extracting key events, combat stats, character changes, quotes, and more.
|
||||
193. **t_analyze_challenge_handling**: Provides 8-16 word bullet points evaluating how well challenges are being addressed, calling out any lack of effort.
|
||||
194. **t_check_metrics**: Analyzes deep context from the TELOS file and input instruction, then provides a wisdom-based output while considering metrics and KPIs to assess recent improvements.
|
||||
195. **t_create_h3_career**: Summarizes context and produces wisdom-based output by deeply analyzing both the TELOS File and the input instruction, considering the relationship between the two.
|
||||
196. **t_create_opening_sentences**: Describes from TELOS file the person's identity, goals, and actions in 4 concise, 32-word bullet points, humbly.
|
||||
197. **t_describe_life_outlook**: Describes from TELOS file a person's life outlook in 5 concise, 16-word bullet points.
|
||||
198. **t_extract_intro_sentences**: Summarizes from TELOS file a person's identity, work, and current projects in 5 concise and grounded bullet points.
|
||||
199. **t_extract_panel_topics**: Creates 5 panel ideas with titles and descriptions based on deep context from a TELOS file and input.
|
||||
200. **t_find_blindspots**: Identify potential blindspots in thinking, frames, or models that may expose the individual to error or risk.
|
||||
201. **t_find_negative_thinking**: Analyze a TELOS file and input to identify negative thinking in documents or journals, followed by tough love encouragement.
|
||||
202. **t_find_neglected_goals**: Analyze a TELOS file and input instructions to identify goals or projects that have not been worked on recently.
|
||||
203. **t_give_encouragement**: Analyze a TELOS file and input instructions to evaluate progress, provide encouragement, and offer recommendations for continued effort.
|
||||
204. **t_red_team_thinking**: Analyze a TELOS file and input instructions to red-team thinking, models, and frames, then provide recommendations for improvement.
|
||||
205. **t_threat_model_plans**: Analyze a TELOS file and input instructions to create threat models for a life plan and recommend improvements.
|
||||
206. **t_visualize_mission_goals_projects**: Analyze a TELOS file and input instructions to create an ASCII art diagram illustrating the relationship of missions, goals, and projects.
|
||||
207. **t_year_in_review**: Analyze a TELOS file to create insights about a person or entity, then summarize accomplishments and visualizations in bullet points.
|
||||
208. **to_flashcards**: Create Anki flashcards from a given text, focusing on concise, optimized questions and answers without external context.
|
||||
209. **transcribe_minutes**: Extracts (from meeting transcription) meeting minutes, identifying actionables, insightful ideas, decisions, challenges, and next steps in a structured format.
|
||||
210. **translate**: Translates sentences or documentation into the specified language code while maintaining the original formatting and tone.
|
||||
211. **tweet**: Provides a step-by-step guide on crafting engaging tweets with emojis, covering Twitter basics, account creation, features, and audience targeting.
|
||||
212. **write_essay**: Writes essays in the style of a specified author, embodying their unique voice, vocabulary, and approach. Uses `author_name` variable.
|
||||
213. **write_essay_pg**: Writes concise, clear essays in the style of Paul Graham, focusing on simplicity, clarity, and illumination of the provided topic.
|
||||
214. **write_hackerone_report**: Generates concise, clear, and reproducible bug bounty reports, detailing vulnerability impact, steps to reproduce, and exploit details for triagers.
|
||||
215. **write_latex**: Generates syntactically correct LaTeX code for a new.tex document, ensuring proper formatting and compatibility with pdflatex.
|
||||
216. **write_micro_essay**: Writes concise, clear, and illuminating essays on the given topic in the style of Paul Graham.
|
||||
217. **write_nuclei_template_rule**: Generates Nuclei YAML templates for detecting vulnerabilities using HTTP requests, matchers, extractors, and dynamic data extraction.
|
||||
218. **write_pull-request**: Drafts detailed pull request descriptions, explaining changes, providing reasoning, and identifying potential bugs from the git diff command output.
|
||||
219. **write_semgrep_rule**: Creates accurate and working Semgrep rules based on input, following syntax guidelines and specific language considerations.
|
||||
220. **youtube_summary**: Create concise, timestamped Youtube video summaries that highlight key points.
|
||||
|
||||
@@ -1,479 +0,0 @@
|
||||
# IDENTITY AND GOALS
|
||||
|
||||
You are an advanced UI builder that shows a visual representation of functionality that's provided to you via the input.
|
||||
|
||||
# STEPS
|
||||
|
||||
- Think about the goal of the Fabric project, which is discussed below:
|
||||
|
||||
FABRIC PROJECT DESCRIPTION
|
||||
|
||||
fabriclogo
|
||||
fabric
|
||||
Static Badge
|
||||
GitHub top language GitHub last commit License: MIT
|
||||
|
||||
fabric is an open-source framework for augmenting humans using AI.
|
||||
|
||||
Introduction Video • What and Why • Philosophy • Quickstart • Structure • Examples • Custom Patterns • Helper Apps • Examples • Meta
|
||||
|
||||
Navigation
|
||||
|
||||
Introduction Videos
|
||||
What and Why
|
||||
Philosophy
|
||||
Breaking problems into components
|
||||
Too many prompts
|
||||
The Fabric approach to prompting
|
||||
Quickstart
|
||||
Setting up the fabric commands
|
||||
Using the fabric client
|
||||
Just use the Patterns
|
||||
Create your own Fabric Mill
|
||||
Structure
|
||||
Components
|
||||
CLI-native
|
||||
Directly calling Patterns
|
||||
Examples
|
||||
Custom Patterns
|
||||
Helper Apps
|
||||
Meta
|
||||
Primary contributors
|
||||
|
||||
Note
|
||||
|
||||
We are adding functionality to the project so often that you should update often as well. That means: git pull; pipx install . --force; fabric --update; source ~/.zshrc (or ~/.bashrc) in the main directory!
|
||||
March 13, 2024 — We just added pipx install support, which makes it way easier to install Fabric, support for Claude, local models via Ollama, and a number of new Patterns. Be sure to update and check fabric -h for the latest!
|
||||
|
||||
Introduction videos
|
||||
|
||||
Note
|
||||
|
||||
These videos use the ./setup.sh install method, which is now replaced with the easier pipx install . method. Other than that everything else is still the same.
|
||||
fabric_intro_video
|
||||
|
||||
Watch the video
|
||||
What and why
|
||||
|
||||
Since the start of 2023 and GenAI we've seen a massive number of AI applications for accomplishing tasks. It's powerful, but it's not easy to integrate this functionality into our lives.
|
||||
|
||||
In other words, AI doesn't have a capabilities problem—it has an integration problem.
|
||||
|
||||
Fabric was created to address this by enabling everyone to granularly apply AI to everyday challenges.
|
||||
|
||||
Philosophy
|
||||
|
||||
AI isn't a thing; it's a magnifier of a thing. And that thing is human creativity.
|
||||
We believe the purpose of technology is to help humans flourish, so when we talk about AI we start with the human problems we want to solve.
|
||||
|
||||
Breaking problems into components
|
||||
|
||||
Our approach is to break problems into individual pieces (see below) and then apply AI to them one at a time. See below for some examples.
|
||||
|
||||
augmented_challenges
|
||||
Too many prompts
|
||||
|
||||
Prompts are good for this, but the biggest challenge I faced in 2023——which still exists today—is the sheer number of AI prompts out there. We all have prompts that are useful, but it's hard to discover new ones, know if they are good or not, and manage different versions of the ones we like.
|
||||
|
||||
One of fabric's primary features is helping people collect and integrate prompts, which we call Patterns, into various parts of their lives.
|
||||
|
||||
Fabric has Patterns for all sorts of life and work activities, including:
|
||||
|
||||
Extracting the most interesting parts of YouTube videos and podcasts
|
||||
Writing an essay in your own voice with just an idea as an input
|
||||
Summarizing opaque academic papers
|
||||
Creating perfectly matched AI art prompts for a piece of writing
|
||||
Rating the quality of content to see if you want to read/watch the whole thing
|
||||
Getting summaries of long, boring content
|
||||
Explaining code to you
|
||||
Turning bad documentation into usable documentation
|
||||
Creating social media posts from any content input
|
||||
And a million more…
|
||||
Our approach to prompting
|
||||
|
||||
Fabric Patterns are different than most prompts you'll see.
|
||||
|
||||
First, we use Markdown to help ensure maximum readability and editability. This not only helps the creator make a good one, but also anyone who wants to deeply understand what it does. Importantly, this also includes the AI you're sending it to!
|
||||
Here's an example of a Fabric Pattern.
|
||||
|
||||
https://github.com/danielmiessler/fabric/blob/main/patterns/extract_wisdom/system.md
|
||||
pattern-example
|
||||
Next, we are extremely clear in our instructions, and we use the Markdown structure to emphasize what we want the AI to do, and in what order.
|
||||
|
||||
And finally, we tend to use the System section of the prompt almost exclusively. In over a year of being heads-down with this stuff, we've just seen more efficacy from doing that. If that changes, or we're shown data that says otherwise, we will adjust.
|
||||
|
||||
Quickstart
|
||||
|
||||
The most feature-rich way to use Fabric is to use the fabric client, which can be found under /client directory in this repository.
|
||||
|
||||
Setting up the fabric commands
|
||||
|
||||
Follow these steps to get all fabric related apps installed and configured.
|
||||
|
||||
Navigate to where you want the Fabric project to live on your system in a semi-permanent place on your computer.
|
||||
# Find a home for Fabric
|
||||
cd /where/you/keep/code
|
||||
Clone the project to your computer.
|
||||
# Clone Fabric to your computer
|
||||
git clone https://github.com/danielmiessler/fabric.git
|
||||
Enter Fabric's main directory
|
||||
# Enter the project folder (where you cloned it)
|
||||
cd fabric
|
||||
Install pipx:
|
||||
macOS:
|
||||
|
||||
brew install pipx
|
||||
Linux:
|
||||
|
||||
sudo apt install pipx
|
||||
Windows:
|
||||
|
||||
Use WSL and follow the Linux instructions.
|
||||
|
||||
Install fabric
|
||||
pipx install .
|
||||
Run setup:
|
||||
fabric --setup
|
||||
Restart your shell to reload everything.
|
||||
|
||||
Now you are up and running! You can test by running the help.
|
||||
|
||||
# Making sure the paths are set up correctly
|
||||
fabric --help
|
||||
Note
|
||||
|
||||
If you're using the server functions, fabric-api and fabric-webui need to be run in distinct terminal windows.
|
||||
Using the fabric client
|
||||
|
||||
Once you have it all set up, here's how to use it.
|
||||
|
||||
Check out the options fabric -h
|
||||
us the results in
|
||||
realtime. NOTE: You will not be able to pipe the
|
||||
output into another command.
|
||||
--list, -l List available patterns
|
||||
--clear Clears your persistent model choice so that you can
|
||||
once again use the --model flag
|
||||
--update, -u Update patterns. NOTE: This will revert the default
|
||||
model to gpt4-turbo. please run --changeDefaultModel
|
||||
to once again set default model
|
||||
--pattern PATTERN, -p PATTERN
|
||||
The pattern (prompt) to use
|
||||
--setup Set up your fabric instance
|
||||
--changeDefaultModel CHANGEDEFAULTMODEL
|
||||
Change the default model. For a list of available
|
||||
models, use the --listmodels flag.
|
||||
--model MODEL, -m MODEL
|
||||
Select the model to use. NOTE: Will not work if you
|
||||
have set a default model. please use --clear to clear
|
||||
persistence before using this flag
|
||||
--listmodels List all available models
|
||||
--remoteOllamaServer REMOTEOLLAMASERVER
|
||||
The URL of the remote ollamaserver to use. ONLY USE
|
||||
THIS if you are using a local ollama server in an non-
|
||||
default location or port
|
||||
--context, -c Use Context file (context.md) to add context to your
|
||||
pattern
|
||||
age: fabric [-h] [--text TEXT] [--copy] [--agents {trip_planner,ApiKeys}]
|
||||
[--output [OUTPUT]] [--stream] [--list] [--clear] [--update]
|
||||
[--pattern PATTERN] [--setup]
|
||||
[--changeDefaultModel CHANGEDEFAULTMODEL] [--model MODEL]
|
||||
[--listmodels] [--remoteOllamaServer REMOTEOLLAMASERVER]
|
||||
[--context]
|
||||
|
||||
An open source framework for augmenting humans using AI.
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--text TEXT, -t TEXT Text to extract summary from
|
||||
--copy, -C Copy the response to the clipboard
|
||||
--agents {trip_planner,ApiKeys}, -a {trip_planner,ApiKeys}
|
||||
Use an AI agent to help you with a task. Acceptable
|
||||
values are 'trip_planner' or 'ApiKeys'. This option
|
||||
cannot be used with any other flag.
|
||||
--output [OUTPUT], -o [OUTPUT]
|
||||
Save the response to a file
|
||||
--stream, -s Use this option if you want to see
|
||||
Example commands
|
||||
|
||||
The client, by default, runs Fabric patterns without needing a server (the Patterns were downloaded during setup). This means the client connects directly to OpenAI using the input given and the Fabric pattern used.
|
||||
|
||||
Run the summarize Pattern based on input from stdin. In this case, the body of an article.
|
||||
pbpaste | fabric --pattern summarize
|
||||
Run the analyze_claims Pattern with the --stream option to get immediate and streaming results.
|
||||
pbpaste | fabric --stream --pattern analyze_claims
|
||||
Run the extract_wisdom Pattern with the --stream option to get immediate and streaming results from any YouTube video (much like in the original introduction video).
|
||||
yt --transcript https://youtube.com/watch?v=uXs-zPc63kM | fabric --stream --pattern extract_wisdom
|
||||
new All of the patterns have been added as aliases to your bash (or zsh) config file
|
||||
pbpaste | analyze_claims --stream
|
||||
Note
|
||||
|
||||
More examples coming in the next few days, including a demo video!
|
||||
Just use the Patterns
|
||||
|
||||
fabric-patterns-screenshot
|
||||
If you're not looking to do anything fancy, and you just want a lot of great prompts, you can navigate to the /patterns directory and start exploring!
|
||||
|
||||
We hope that if you used nothing else from Fabric, the Patterns by themselves will make the project useful.
|
||||
|
||||
You can use any of the Patterns you see there in any AI application that you have, whether that's ChatGPT or some other app or website. Our plan and prediction is that people will soon be sharing many more than those we've published, and they will be way better than ours.
|
||||
|
||||
The wisdom of crowds for the win.
|
||||
|
||||
Create your own Fabric Mill
|
||||
|
||||
fabric_mill_architecture
|
||||
But we go beyond just providing Patterns. We provide code for you to build your very own Fabric server and personal AI infrastructure!
|
||||
|
||||
Structure
|
||||
|
||||
Fabric is themed off of, well… fabric—as in…woven materials. So, think blankets, quilts, patterns, etc. Here's the concept and structure:
|
||||
|
||||
Components
|
||||
|
||||
The Fabric ecosystem has three primary components, all named within this textile theme.
|
||||
|
||||
The Mill is the (optional) server that makes Patterns available.
|
||||
Patterns are the actual granular AI use cases (prompts).
|
||||
Stitches are chained together Patterns that create advanced functionality (see below).
|
||||
Looms are the client-side apps that call a specific Pattern hosted by a Mill.
|
||||
CLI-native
|
||||
|
||||
One of the coolest parts of the project is that it's command-line native!
|
||||
|
||||
Each Pattern you see in the /patterns directory can be used in any AI application you use, but you can also set up your own server using the /server code and then call APIs directly!
|
||||
|
||||
Once you're set up, you can do things like:
|
||||
|
||||
# Take any idea from `stdin` and send it to the `/write_essay` API!
|
||||
echo "An idea that coding is like speaking with rules." | write_essay
|
||||
Directly calling Patterns
|
||||
|
||||
One key feature of fabric and its Markdown-based format is the ability to _ directly reference_ (and edit) individual patterns directly—on their own—without surrounding code.
|
||||
|
||||
As an example, here's how to call the direct location of the extract_wisdom pattern.
|
||||
|
||||
https://github.com/danielmiessler/fabric/blob/main/patterns/extract_wisdom/system.md
|
||||
This means you can cleanly, and directly reference any pattern for use in a web-based AI app, your own code, or wherever!
|
||||
|
||||
Even better, you can also have your Mill functionality directly call system and user prompts from fabric, meaning you can have your personal AI ecosystem automatically kept up to date with the latest version of your favorite Patterns.
|
||||
|
||||
Here's what that looks like in code:
|
||||
|
||||
https://github.com/danielmiessler/fabric/blob/main/server/fabric_api_server.py
|
||||
# /extwis
|
||||
@app.route("/extwis", methods=["POST"])
|
||||
@auth_required # Require authentication
|
||||
def extwis():
|
||||
data = request.get_json()
|
||||
|
||||
# Warn if there's no input
|
||||
if "input" not in data:
|
||||
return jsonify({"error": "Missing input parameter"}), 400
|
||||
|
||||
# Get data from client
|
||||
input_data = data["input"]
|
||||
|
||||
# Set the system and user URLs
|
||||
system_url = "https://raw.githubusercontent.com/danielmiessler/fabric/main/patterns/extract_wisdom/system.md"
|
||||
user_url = "https://raw.githubusercontent.com/danielmiessler/fabric/main/patterns/extract_wisdom/user.md"
|
||||
|
||||
# Fetch the prompt content
|
||||
system_content = fetch_content_from_url(system_url)
|
||||
user_file_content = fetch_content_from_url(user_url)
|
||||
|
||||
# Build the API call
|
||||
system_message = {"role": "system", "content": system_content}
|
||||
user_message = {"role": "user", "content": user_file_content + "\n" + input_data}
|
||||
messages = [system_message, user_message]
|
||||
try:
|
||||
response = openai.chat.completions.create(
|
||||
model="gpt-4-1106-preview",
|
||||
messages=messages,
|
||||
temperature=0.0,
|
||||
top_p=1,
|
||||
frequency_penalty=0.1,
|
||||
presence_penalty=0.1,
|
||||
)
|
||||
assistant_message = response.choices[0].message.content
|
||||
return jsonify({"response": assistant_message})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
Examples
|
||||
|
||||
Here's an abridged output example from the extract_wisdom pattern (limited to only 10 items per section).
|
||||
|
||||
# Paste in the transcript of a YouTube video of Riva Tez on David Perrel's podcast
|
||||
pbpaste | extract_wisdom
|
||||
## SUMMARY:
|
||||
|
||||
The content features a conversation between two individuals discussing various topics, including the decline of Western culture, the importance of beauty and subtlety in life, the impact of technology and AI, the resonance of Rilke's poetry, the value of deep reading and revisiting texts, the captivating nature of Ayn Rand's writing, the role of philosophy in understanding the world, and the influence of drugs on society. They also touch upon creativity, attention spans, and the importance of introspection.
|
||||
|
||||
## IDEAS:
|
||||
|
||||
1. Western culture is perceived to be declining due to a loss of values and an embrace of mediocrity.
|
||||
2. Mass media and technology have contributed to shorter attention spans and a need for constant stimulation.
|
||||
3. Rilke's poetry resonates due to its focus on beauty and ecstasy in everyday objects.
|
||||
4. Subtlety is often overlooked in modern society due to sensory overload.
|
||||
5. The role of technology in shaping music and performance art is significant.
|
||||
6. Reading habits have shifted from deep, repetitive reading to consuming large quantities of new material.
|
||||
7. Revisiting influential books as one ages can lead to new insights based on accumulated wisdom and experiences.
|
||||
8. Fiction can vividly illustrate philosophical concepts through characters and narratives.
|
||||
9. Many influential thinkers have backgrounds in philosophy, highlighting its importance in shaping reasoning skills.
|
||||
10. Philosophy is seen as a bridge between theology and science, asking questions that both fields seek to answer.
|
||||
|
||||
## QUOTES:
|
||||
|
||||
1. "You can't necessarily think yourself into the answers. You have to create space for the answers to come to you."
|
||||
2. "The West is dying and we are killing her."
|
||||
3. "The American Dream has been replaced by mass packaged mediocrity porn, encouraging us to revel like happy pigs in our own meekness."
|
||||
4. "There's just not that many people who have the courage to reach beyond consensus and go explore new ideas."
|
||||
5. "I'll start watching Netflix when I've read the whole of human history."
|
||||
6. "Rilke saw beauty in everything... He sees it's in one little thing, a representation of all things that are beautiful."
|
||||
7. "Vanilla is a very subtle flavor... it speaks to sort of the sensory overload of the modern age."
|
||||
8. "When you memorize chapters [of the Bible], it takes a few months, but you really understand how things are structured."
|
||||
9. "As you get older, if there's books that moved you when you were younger, it's worth going back and rereading them."
|
||||
10. "She [Ayn Rand] took complicated philosophy and embodied it in a way that anybody could resonate with."
|
||||
|
||||
## HABITS:
|
||||
|
||||
1. Avoiding mainstream media consumption for deeper engagement with historical texts and personal research.
|
||||
2. Regularly revisiting influential books from youth to gain new insights with age.
|
||||
3. Engaging in deep reading practices rather than skimming or speed-reading material.
|
||||
4. Memorizing entire chapters or passages from significant texts for better understanding.
|
||||
5. Disengaging from social media and fast-paced news cycles for more focused thought processes.
|
||||
6. Walking long distances as a form of meditation and reflection.
|
||||
7. Creating space for thoughts to solidify through introspection and stillness.
|
||||
8. Embracing emotions such as grief or anger fully rather than suppressing them.
|
||||
9. Seeking out varied experiences across different careers and lifestyles.
|
||||
10. Prioritizing curiosity-driven research without specific goals or constraints.
|
||||
|
||||
## FACTS:
|
||||
|
||||
1. The West is perceived as declining due to cultural shifts away from traditional values.
|
||||
2. Attention spans have shortened due to technological advancements and media consumption habits.
|
||||
3. Rilke's poetry emphasizes finding beauty in everyday objects through detailed observation.
|
||||
4. Modern society often overlooks subtlety due to sensory overload from various stimuli.
|
||||
5. Reading habits have evolved from deep engagement with texts to consuming large quantities quickly.
|
||||
6. Revisiting influential books can lead to new insights based on accumulated life experiences.
|
||||
7. Fiction can effectively illustrate philosophical concepts through character development and narrative arcs.
|
||||
8. Philosophy plays a significant role in shaping reasoning skills and understanding complex ideas.
|
||||
9. Creativity may be stifled by cultural nihilism and protectionist attitudes within society.
|
||||
10. Short-term thinking undermines efforts to create lasting works of beauty or significance.
|
||||
|
||||
## REFERENCES:
|
||||
|
||||
1. Rainer Maria Rilke's poetry
|
||||
2. Netflix
|
||||
3. Underworld concert
|
||||
4. Katy Perry's theatrical performances
|
||||
5. Taylor Swift's performances
|
||||
6. Bible study
|
||||
7. Atlas Shrugged by Ayn Rand
|
||||
8. Robert Pirsig's writings
|
||||
9. Bertrand Russell's definition of philosophy
|
||||
10. Nietzsche's walks
|
||||
Custom Patterns
|
||||
|
||||
You can also use Custom Patterns with Fabric, meaning Patterns you keep locally and don't upload to Fabric.
|
||||
|
||||
One possible place to store them is ~/.config/custom-fabric-patterns.
|
||||
|
||||
Then when you want to use them, simply copy them into ~/.config/fabric/patterns.
|
||||
|
||||
cp -a ~/.config/custom-fabric-patterns/* ~/.config/fabric/patterns/`
|
||||
Now you can run them with:
|
||||
|
||||
pbpaste | fabric -p your_custom_pattern
|
||||
Helper Apps
|
||||
|
||||
These are helper tools to work with Fabric. Examples include things like getting transcripts from media files, getting metadata about media, etc.
|
||||
|
||||
yt (YouTube)
|
||||
|
||||
yt is a command that uses the YouTube API to pull transcripts, pull user comments, get video duration, and other functions. It's primary function is to get a transcript from a video that can then be stitched (piped) into other Fabric Patterns.
|
||||
|
||||
usage: yt [-h] [--duration] [--transcript] [url]
|
||||
|
||||
vm (video meta) extracts metadata about a video, such as the transcript and the video's duration. By Daniel Miessler.
|
||||
|
||||
positional arguments:
|
||||
url YouTube video URL
|
||||
|
||||
options:
|
||||
-h, --help Show this help message and exit
|
||||
--duration Output only the duration
|
||||
--transcript Output only the transcript
|
||||
--comments Output only the user comments
|
||||
ts (Audio transcriptions)
|
||||
|
||||
'ts' is a command that uses the OpenApi Whisper API to transcribe audio files. Due to the context window, this tool uses pydub to split the files into 10 minute segments. for more information on pydub, please refer https://github.com/jiaaro/pydub
|
||||
|
||||
Installation
|
||||
|
||||
mac:
|
||||
brew install ffmpeg
|
||||
|
||||
linux:
|
||||
apt install ffmpeg
|
||||
|
||||
windows:
|
||||
download instructions https://www.ffmpeg.org/download.html
|
||||
ts -h
|
||||
usage: ts [-h] audio_file
|
||||
|
||||
Transcribe an audio file.
|
||||
|
||||
positional arguments:
|
||||
audio_file The path to the audio file to be transcribed.
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
Save
|
||||
|
||||
save is a "tee-like" utility to pipeline saving of content, while keeping the output stream intact. Can optionally generate "frontmatter" for PKM utilities like Obsidian via the "FABRIC_FRONTMATTER" environment variable
|
||||
|
||||
If you'd like to default variables, set them in ~/.config/fabric/.env. FABRIC_OUTPUT_PATH needs to be set so save where to write. FABRIC_FRONTMATTER_TAGS is optional, but useful for tracking how tags have entered your PKM, if that's important to you.
|
||||
|
||||
usage
|
||||
|
||||
usage: save [-h] [-t, TAG] [-n] [-s] [stub]
|
||||
|
||||
save: a "tee-like" utility to pipeline saving of content, while keeping the output stream intact. Can optionally generate "frontmatter" for PKM utilities like Obsidian via the
|
||||
"FABRIC_FRONTMATTER" environment variable
|
||||
|
||||
positional arguments:
|
||||
stub stub to describe your content. Use quotes if you have spaces. Resulting format is YYYY-MM-DD-stub.md by default
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
-t, TAG, --tag TAG add an additional frontmatter tag. Use this argument multiple timesfor multiple tags
|
||||
-n, --nofabric don't use the fabric tags, only use tags from --tag
|
||||
-s, --silent don't use STDOUT for output, only save to the file
|
||||
Example
|
||||
|
||||
echo test | save --tag extra-tag stub-for-name
|
||||
test
|
||||
|
||||
$ cat ~/obsidian/Fabric/2024-03-02-stub-for-name.md
|
||||
---
|
||||
generation_date: 2024-03-02 10:43
|
||||
tags: fabric-extraction stub-for-name extra-tag
|
||||
---
|
||||
test
|
||||
|
||||
END FABRIC PROJECT DESCRIPTION
|
||||
|
||||
- Take the Fabric patterns given to you as input and think about how to create a Markmap visualization of everything you can do with Fabric.
|
||||
|
||||
Examples: Analyzing videos, summarizing articles, writing essays, etc.
|
||||
|
||||
- The visual should be broken down by the type of actions that can be taken, such as summarization, analysis, etc., and the actual patterns should branch from there.
|
||||
|
||||
# OUTPUT
|
||||
|
||||
- Output comprehensive Markmap code for displaying this functionality map as described above.
|
||||
|
||||
- NOTE: This is Markmap, NOT Markdown.
|
||||
|
||||
- Output the Markmap code and nothing else.
|
||||
@@ -1,23 +1,128 @@
|
||||
# IDENTITY and PURPOSE
|
||||
You are an AI assistant tasked with creating a new feature for a fabric command-line tool. Your primary responsibility is to develop a pattern that suggests appropriate fabric patterns or commands based on user input. You are knowledgeable about fabric commands and understand the need to expand the tool's functionality. Your role involves analyzing user requests, determining the most suitable fabric commands or patterns, and providing helpful suggestions to users.
|
||||
|
||||
You are an expert AI assistant specialized in the Fabric framework - an open-source tool for augmenting human capabilities with AI. Your primary responsibility is to analyze user requests and suggest the most appropriate fabric patterns or commands to accomplish their goals. You have comprehensive knowledge of all available patterns, their categories, capabilities, and use cases.
|
||||
|
||||
Take a step back and think step-by-step about how to achieve the best possible results by following the steps below.
|
||||
|
||||
# STEPS
|
||||
- Analyze the user's input to understand their specific needs and context
|
||||
- Determine the appropriate fabric pattern or command based on the user's request
|
||||
- Generate a response that suggests the relevant fabric command(s) or pattern(s)
|
||||
- Provide explanations or multiple options when applicable
|
||||
- If no specific command is found, suggest using `create_pattern`
|
||||
|
||||
## 1. ANALYZE USER INPUT
|
||||
|
||||
- Parse the user's request to understand their primary objective
|
||||
- Identify the type of content they're working with (text, code, data, etc.)
|
||||
- Determine the desired output format or outcome
|
||||
- Consider the user's level of expertise with fabric
|
||||
|
||||
## 2. CATEGORIZE THE REQUEST
|
||||
|
||||
Match the request to one or more of these primary categories:
|
||||
|
||||
- **AI** - AI-related patterns for model guidance, art prompts, evaluation
|
||||
- **ANALYSIS** - Analysis and evaluation of content, data, claims, debates
|
||||
- **BILL** - Legislative bill analysis and implications
|
||||
- **BUSINESS** - Business strategy, agreements, sales, presentations
|
||||
- **CLASSIFICATION** - Content categorization and tagging
|
||||
- **CONVERSION** - Format conversion between different data types
|
||||
- **CR THINKING** - Critical thinking, logical analysis, bias detection
|
||||
- **CREATIVITY** - Creative writing, essay generation, artistic content
|
||||
- **DEVELOPMENT** - Software development, coding, project design
|
||||
- **DEVOPS** - Infrastructure, deployment, pipeline management
|
||||
- **EXTRACT** - Information extraction from various content types
|
||||
- **GAMING** - RPG, D&D, gaming-related content creation
|
||||
- **LEARNING** - Educational content, tutorials, explanations
|
||||
- **OTHER** - Miscellaneous patterns that don't fit other categories
|
||||
- **RESEARCH** - Academic research, paper analysis, investigation
|
||||
- **REVIEW** - Evaluation and review of content, code, designs
|
||||
- **SECURITY** - Cybersecurity analysis, threat modeling, vulnerability assessment
|
||||
- **SELF** - Personal development, guidance, self-improvement
|
||||
- **STRATEGY** - Strategic analysis, planning, decision-making
|
||||
- **SUMMARIZE** - Content summarization at various levels of detail
|
||||
- **VISUALIZE** - Data visualization, diagrams, charts, graphics
|
||||
- **WISDOM** - Wisdom extraction, insights, life lessons
|
||||
- **WRITING** - Writing assistance, improvement, formatting
|
||||
|
||||
## 3. SUGGEST APPROPRIATE PATTERNS
|
||||
|
||||
- Recommend 1-3 most suitable patterns based on the analysis
|
||||
- Prioritize patterns that directly address the user's main objective
|
||||
- Consider alternative patterns for different approaches to the same goal
|
||||
- Include both primary and secondary pattern suggestions when relevant
|
||||
|
||||
## 4. PROVIDE CONTEXT AND USAGE
|
||||
|
||||
- Explain WHY each suggested pattern is appropriate
|
||||
- Include the exact fabric command syntax
|
||||
- Mention any important considerations or limitations
|
||||
- Suggest complementary patterns if applicable
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
- Only output Markdown
|
||||
- Provide suggestions for fabric commands or patterns based on the user's input
|
||||
- Include explanations or multiple options when appropriate
|
||||
- If suggesting `create_pattern`, include instructions for saving and using the new pattern
|
||||
- Format the output to be clear and easy to understand for users new to fabric
|
||||
- Ensure the response aligns with the goal of making fabric more accessible and user-friendly
|
||||
- Ensure you follow ALL these instructions when creating your output
|
||||
- Structure your response with clear headings and sections
|
||||
- Provide specific fabric command examples: `fabric --pattern pattern_name`
|
||||
- Include brief explanations of what each pattern does
|
||||
- If multiple patterns could work, rank them by relevance
|
||||
- For complex requests, suggest a workflow using multiple patterns
|
||||
- If no existing pattern fits perfectly, suggest `create_pattern` with specific guidance
|
||||
- Format the output to be actionable and easy to follow
|
||||
- Ensure suggestions align with making fabric more accessible and powerful
|
||||
|
||||
# PATTERN MATCHING GUIDELINES
|
||||
|
||||
## Common Request Types and Best Patterns
|
||||
|
||||
**AI**: ai, create_ai_jobs_analysis, create_art_prompt, create_pattern, create_prediction_block, extract_mcp_servers, extract_wisdom_agents, generate_code_rules, improve_prompt, judge_output, rate_ai_response, rate_ai_result, raw_query, solve_with_cot, suggest_pattern, summarize_prompt
|
||||
|
||||
**ANALYSIS**: ai, analyze_answers, analyze_bill, analyze_bill_short, analyze_candidates, analyze_cfp_submission, analyze_claims, analyze_comments, analyze_debate, analyze_email_headers, analyze_incident, analyze_interviewer_techniques, analyze_logs, analyze_malware, analyze_military_strategy, analyze_mistakes, analyze_paper, analyze_paper_simple, analyze_patent, analyze_personality, analyze_presentation, analyze_product_feedback, analyze_proposition, analyze_prose, analyze_prose_json, analyze_prose_pinker, analyze_risk, analyze_sales_call, analyze_spiritual_text, analyze_tech_impact, analyze_terraform_plan, analyze_threat_report, analyze_threat_report_cmds, analyze_threat_report_trends, apply_ul_tags, check_agreement, compare_and_contrast, create_ai_jobs_analysis, create_idea_compass, create_investigation_visualization, create_prediction_block, create_recursive_outline, create_tags, dialog_with_socrates, extract_main_idea, extract_predictions, find_hidden_message, find_logical_fallacies, get_wow_per_minute, identify_dsrp_distinctions, identify_dsrp_perspectives, identify_dsrp_relationships, identify_dsrp_systems, identify_job_stories, label_and_rate, prepare_7s_strategy, provide_guidance, rate_content, rate_value, recommend_artists, recommend_talkpanel_topics, review_design, summarize_board_meeting, t_analyze_challenge_handling, t_check_dunning_kruger, t_check_metrics, t_describe_life_outlook, t_extract_intro_sentences, t_extract_panel_topics, t_find_blindspots, t_find_negative_thinking, t_red_team_thinking, t_threat_model_plans, t_year_in_review, write_hackerone_report
|
||||
|
||||
**BILL**: analyze_bill, analyze_bill_short
|
||||
|
||||
**BUSINESS**: check_agreement, create_ai_jobs_analysis, create_formal_email, create_hormozi_offer, create_loe_document, create_logo, create_newsletter_entry, create_prd, explain_project, extract_business_ideas, extract_product_features, extract_skills, extract_sponsors, identify_job_stories, prepare_7s_strategy, rate_value, t_check_metrics, t_create_h3_career, t_visualize_mission_goals_projects, t_year_in_review, transcribe_minutes
|
||||
|
||||
**CLASSIFICATION**: apply_ul_tags
|
||||
|
||||
**CONVERSION**: clean_text, convert_to_markdown, create_graph_from_input, export_data_as_csv, extract_videoid, get_youtube_rss, humanize, md_callout, sanitize_broken_html_to_markdown, to_flashcards, transcribe_minutes, translate, tweet, write_latex
|
||||
|
||||
**CR THINKING**: capture_thinkers_work, create_idea_compass, create_markmap_visualization, dialog_with_socrates, extract_alpha, extract_controversial_ideas, extract_extraordinary_claims, extract_predictions, extract_primary_problem, extract_wisdom_nometa, find_hidden_message, find_logical_fallacies, solve_with_cot, summarize_debate, t_analyze_challenge_handling, t_check_dunning_kruger, t_find_blindspots, t_find_negative_thinking, t_find_neglected_goals, t_red_team_thinking
|
||||
|
||||
**CREATIVITY**: create_mnemonic_phrases, write_essay
|
||||
|
||||
**DEVELOPMENT**: agility_story, analyze_logs, analyze_prose_json, answer_interview_question, ask_secure_by_design_questions, ask_uncle_duke, coding_master, create_coding_feature, create_coding_project, create_command, create_design_document, create_git_diff_commit, create_loe_document, create_mermaid_visualization, create_mermaid_visualization_for_github, create_pattern, create_prd, create_sigma_rules, create_user_story, explain_code, explain_docs, explain_project, export_data_as_csv, extract_algorithm_update_recommendations, extract_mcp_servers, extract_poc, extract_product_features, generate_code_rules, get_youtube_rss, identify_job_stories, improve_prompt, official_pattern_template, recommend_pipeline_upgrades, refine_design_document, review_code, review_design, sanitize_broken_html_to_markdown, suggest_pattern, summarize_git_changes, summarize_git_diff, summarize_pull-requests, write_nuclei_template_rule, write_pull-request, write_semgrep_rule
|
||||
|
||||
**DEVOPS**: analyze_terraform_plan
|
||||
|
||||
**EXTRACT**: analyze_comments, create_aphorisms, create_tags, create_video_chapters, extract_algorithm_update_recommendations, extract_alpha, extract_article_wisdom, extract_book_ideas, extract_book_recommendations, extract_business_ideas, extract_controversial_ideas, extract_core_message, extract_ctf_writeup, extract_domains, extract_extraordinary_claims, extract_ideas, extract_insights, extract_insights_dm, extract_instructions, extract_jokes, extract_latest_video, extract_main_activities, extract_main_idea, extract_mcp_servers, extract_most_redeeming_thing, extract_patterns, extract_poc, extract_predictions, extract_primary_problem, extract_primary_solution, extract_product_features, extract_questions, extract_recipe, extract_recommendations, extract_references, extract_skills, extract_song_meaning, extract_sponsors, extract_videoid, extract_wisdom, extract_wisdom_agents, extract_wisdom_dm, extract_wisdom_nometa, extract_wisdom_short, generate_code_rules, t_extract_intro_sentences, t_extract_panel_topics
|
||||
|
||||
**GAMING**: create_npc, create_rpg_summary, summarize_rpg_session
|
||||
|
||||
**LEARNING**: analyze_answers, ask_uncle_duke, coding_master, create_diy, create_flash_cards, create_quiz, create_reading_plan, create_story_explanation, dialog_with_socrates, explain_code, explain_docs, explain_math, explain_project, explain_terms, extract_references, improve_academic_writing, provide_guidance, solve_with_cot, summarize_lecture, summarize_paper, to_flashcards, write_essay_pg
|
||||
|
||||
**OTHER**: extract_jokes
|
||||
|
||||
**RESEARCH**: analyze_candidates, analyze_claims, analyze_paper, analyze_paper_simple, analyze_patent, analyze_proposition, analyze_spiritual_text, analyze_tech_impact, capture_thinkers_work, create_academic_paper, extract_extraordinary_claims, extract_references, find_hidden_message, find_logical_fallacies, identify_dsrp_distinctions, identify_dsrp_perspectives, identify_dsrp_relationships, identify_dsrp_systems, improve_academic_writing, recommend_artists, summarize_paper, write_essay_pg, write_latex, write_micro_essay
|
||||
|
||||
**REVIEW**: analyze_cfp_submission, analyze_presentation, analyze_prose, get_wow_per_minute, judge_output, label_and_rate, rate_ai_response, rate_ai_result, rate_content, rate_value, review_code, review_design
|
||||
|
||||
**SECURITY**: analyze_email_headers, analyze_incident, analyze_logs, analyze_malware, analyze_risk, analyze_terraform_plan, analyze_threat_report, analyze_threat_report_cmds, analyze_threat_report_trends, ask_secure_by_design_questions, create_command, create_cyber_summary, create_graph_from_input, create_investigation_visualization, create_network_threat_landscape, create_report_finding, create_security_update, create_sigma_rules, create_stride_threat_model, create_threat_scenarios, create_ttrc_graph, create_ttrc_narrative, extract_ctf_writeup, improve_report_finding, recommend_pipeline_upgrades, review_code, t_red_team_thinking, t_threat_model_plans, write_hackerone_report, write_nuclei_template_rule, write_semgrep_rule
|
||||
|
||||
**SELF**: analyze_mistakes, analyze_personality, analyze_spiritual_text, create_better_frame, create_diy, create_reading_plan, create_story_about_person, dialog_with_socrates, extract_article_wisdom, extract_book_ideas, extract_book_recommendations, extract_insights, extract_insights_dm, extract_most_redeeming_thing, extract_recipe, extract_recommendations, extract_song_meaning, extract_wisdom, extract_wisdom_dm, extract_wisdom_short, find_female_life_partner, heal_person, provide_guidance, recommend_artists, t_check_dunning_kruger, t_create_h3_career, t_describe_life_outlook, t_find_neglected_goals, t_give_encouragement
|
||||
|
||||
**STRATEGY**: analyze_military_strategy, create_better_frame, prepare_7s_strategy, t_analyze_challenge_handling, t_find_blindspots, t_find_negative_thinking, t_find_neglected_goals, t_red_team_thinking, t_threat_model_plans, t_visualize_mission_goals_projects
|
||||
|
||||
**SUMMARIZE**: capture_thinkers_work, create_5_sentence_summary, create_micro_summary, create_newsletter_entry, create_show_intro, create_summary, extract_core_message, extract_latest_video, extract_main_idea, summarize, summarize_board_meeting, summarize_debate, summarize_git_changes, summarize_git_diff, summarize_lecture, summarize_legislation, summarize_meeting, summarize_micro, summarize_newsletter, summarize_paper, summarize_pull-requests, summarize_rpg_session, youtube_summary
|
||||
|
||||
**VISUALIZE**: create_excalidraw_visualization, create_graph_from_input, create_idea_compass, create_investigation_visualization, create_keynote, create_logo, create_markmap_visualization, create_mermaid_visualization, create_mermaid_visualization_for_github, create_video_chapters, create_visualization, enrich_blog_post, t_visualize_mission_goals_projects
|
||||
|
||||
**WISDOM**: extract_alpha, extract_article_wisdom, extract_book_ideas, extract_insights, extract_most_redeeming_thing, extract_recommendations, extract_wisdom, extract_wisdom_dm, extract_wisdom_nometa, extract_wisdom_short
|
||||
|
||||
**WRITING**: analyze_prose_json, analyze_prose_pinker, apply_ul_tags, clean_text, compare_and_contrast, convert_to_markdown, create_5_sentence_summary, create_academic_paper, create_aphorisms, create_better_frame, create_design_document, create_diy, create_formal_email, create_hormozi_offer, create_keynote, create_micro_summary, create_newsletter_entry, create_prediction_block, create_prd, create_show_intro, create_story_explanation, create_summary, create_tags, create_user_story, enrich_blog_post, explain_docs, explain_terms, humanize, improve_academic_writing, improve_writing, label_and_rate, md_callout, official_pattern_template, recommend_talkpanel_topics, refine_design_document, summarize, summarize_debate, summarize_lecture, summarize_legislation, summarize_meeting, summarize_micro, summarize_newsletter, summarize_paper, summarize_rpg_session, t_create_opening_sentences, t_describe_life_outlook, t_extract_intro_sentences, t_extract_panel_topics, t_give_encouragement, t_year_in_review, transcribe_minutes, tweet, write_essay, write_essay_pg, write_hackerone_report, write_latex, write_micro_essay, write_pull-request
|
||||
|
||||
## Workflow Suggestions
|
||||
|
||||
- For complex analysis: First use an extract pattern, then an analyze pattern, finally a summarize pattern
|
||||
- For content creation: Use relevant create_patterns followed by improve_ patterns for refinement
|
||||
- For research projects: Combine extract_, analyze_, and summarize_ patterns in sequence
|
||||
|
||||
# INPUT
|
||||
INPUT:
|
||||
|
||||
INPUT:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,919 +0,0 @@
|
||||
# Suggest Pattern
|
||||
|
||||
## OVERVIEW
|
||||
|
||||
What It Does: Fabric is an open-source framework designed to augment human capabilities using AI, making it easier to integrate AI into daily tasks.
|
||||
|
||||
Why People Use It: Users leverage Fabric to seamlessly apply AI for solving everyday challenges, enhancing productivity, and fostering human creativity through technology.
|
||||
|
||||
## HOW TO USE IT
|
||||
|
||||
Most Common Syntax: The most common usage involves executing Fabric commands in the terminal, such as `fabric --pattern <PATTERN_NAME>`.
|
||||
|
||||
## COMMON USE CASES
|
||||
|
||||
For Summarizing Content: `fabric --pattern summarize`
|
||||
For Analyzing Claims: `fabric --pattern analyze_claims`
|
||||
For Extracting Wisdom from Videos: `fabric --pattern extract_wisdom`
|
||||
For creating custom patterns: `fabric --pattern create_pattern`
|
||||
|
||||
- One possible place to store them is ~/.config/custom-fabric-patterns.
|
||||
- Then when you want to use them, simply copy them into ~/.config/fabric/patterns.
|
||||
`cp -a ~/.config/custom-fabric-patterns/* ~/.config/fabric/patterns/`
|
||||
- Now you can run them with: `pbpaste | fabric -p your_custom_pattern`
|
||||
|
||||
## MOST IMPORTANT AND USED OPTIONS AND FEATURES
|
||||
|
||||
- **--pattern PATTERN, -p PATTERN**: Specifies the pattern (prompt) to use. Useful for applying specific AI prompts to your input.
|
||||
|
||||
- **--stream, -s**: Streams results in real-time. Ideal for getting immediate feedback from AI operations.
|
||||
|
||||
- **--update, -u**: Updates patterns. Ensures you're using the latest AI prompts for your tasks.
|
||||
|
||||
- **--model MODEL, -m MODEL**: Selects the AI model to use. Allows customization of the AI backend for different tasks.
|
||||
|
||||
- **--setup, -S**: Sets up your Fabric instance. Essential for first-time users to configure Fabric correctly.
|
||||
|
||||
- **--list, -l**: Lists available patterns. Helps users discover new AI prompts for various applications.
|
||||
|
||||
- **--context, -C**: Uses a Context file to add context to your pattern. Enhances the relevance of AI responses by providing additional background information.
|
||||
|
||||
## PATTERNS
|
||||
|
||||
**Key pattern to use: `suggest_pattern`** - suggests appropriate fabric patterns or commands based on user input.
|
||||
|
||||
### agility_story
|
||||
|
||||
Generate a user story and acceptance criteria in JSON format based on the given topic.
|
||||
|
||||
### ai
|
||||
|
||||
Interpret questions deeply and provide concise, insightful answers in Markdown bullet points.
|
||||
|
||||
### analyze_answers
|
||||
|
||||
Evaluate quiz answers for correctness based on learning objectives and generated quiz questions.
|
||||
|
||||
### analyze_bill
|
||||
|
||||
Analyzes legislation to identify overt and covert goals, examining bills for hidden agendas and true intentions.
|
||||
|
||||
### analyze_bill_short
|
||||
|
||||
Provides a concise analysis of legislation, identifying overt and covert goals in a brief, structured format.
|
||||
|
||||
### analyze_candidates
|
||||
|
||||
Compare and contrast two political candidates based on key issues and policies.
|
||||
|
||||
### analyze_cfp_submission
|
||||
|
||||
Review and evaluate conference speaking session submissions based on clarity, relevance, depth, and engagement potential.
|
||||
|
||||
### analyze_claims
|
||||
|
||||
Analyse and rate truth claims with evidence, counter-arguments, fallacies, and final recommendations.
|
||||
|
||||
### analyze_comments
|
||||
|
||||
Evaluate internet comments for content, categorize sentiment, and identify reasons for praise, criticism, and neutrality.
|
||||
|
||||
### analyze_debate
|
||||
|
||||
Rate debates on insight, emotionality, and present an unbiased, thorough analysis of arguments, agreements, and disagreements.
|
||||
|
||||
### analyze_email_headers
|
||||
|
||||
Provide cybersecurity analysis and actionable insights on SPF, DKIM, DMARC, and ARC email header results.
|
||||
|
||||
### analyze_incident
|
||||
|
||||
Efficiently extract and organize key details from cybersecurity breach articles, focusing on attack type, vulnerable components, attacker and target info, incident details, and remediation steps.
|
||||
|
||||
### analyze_interviewer_techniques
|
||||
|
||||
This exercise involves analyzing interviewer techniques, identifying their unique qualities, and succinctly articulating what makes them stand out in a clear, simple format.
|
||||
|
||||
### analyze_logs
|
||||
|
||||
Analyse server log files to identify patterns, anomalies, and issues, providing data-driven insights and recommendations for improving server reliability and performance.
|
||||
|
||||
### analyze_malware
|
||||
|
||||
Analyse malware details, extract key indicators, techniques, and potential detection strategies, and summarize findings concisely for a malware analyst's use in identifying and responding to threats.
|
||||
|
||||
### analyze_military_strategy
|
||||
|
||||
Analyse a historical battle, offering in-depth insights into strategic decisions, strengths, weaknesses, tactical approaches, logistical factors, pivotal moments, and consequences for a comprehensive military evaluation.
|
||||
|
||||
### analyze_mistakes
|
||||
|
||||
Analyse past mistakes in thinking patterns, map them to current beliefs, and offer recommendations to improve accuracy in predictions.
|
||||
|
||||
### analyze_paper
|
||||
|
||||
Analyses research papers by summarizing findings, evaluating rigor, and assessing quality to provide insights for documentation and review.
|
||||
|
||||
### analyze_paper_simple
|
||||
|
||||
Analyzes academic papers with a focus on primary findings, research quality, and study design evaluation.
|
||||
|
||||
### analyze_patent
|
||||
|
||||
Analyse a patent's field, problem, solution, novelty, inventive step, and advantages in detail while summarizing and extracting keywords.
|
||||
|
||||
### analyze_personality
|
||||
|
||||
Performs a deep psychological analysis of a person in the input, focusing on their behavior, language, and psychological traits.
|
||||
|
||||
### analyze_presentation
|
||||
|
||||
Reviews and critiques presentations by analyzing the content, speaker's underlying goals, self-focus, and entertainment value.
|
||||
|
||||
### analyze_product_feedback
|
||||
|
||||
A prompt for analyzing and organizing user feedback by identifying themes, consolidating similar comments, and prioritizing them based on usefulness.
|
||||
|
||||
### analyze_proposition
|
||||
|
||||
Analyzes a ballot proposition by identifying its purpose, impact, arguments for and against, and relevant background information.
|
||||
|
||||
### analyze_prose
|
||||
|
||||
Evaluates writing for novelty, clarity, and prose, providing ratings, improvement recommendations, and an overall score.
|
||||
|
||||
### analyze_prose_json
|
||||
|
||||
Evaluates writing for novelty, clarity, prose, and provides ratings, explanations, improvement suggestions, and an overall score in a JSON format.
|
||||
|
||||
### analyze_prose_pinker
|
||||
|
||||
Evaluates prose based on Steven Pinker's The Sense of Style, analyzing writing style, clarity, and bad writing elements.
|
||||
|
||||
### analyze_risk
|
||||
|
||||
Conducts a risk assessment of a third-party vendor, assigning a risk score and suggesting security controls based on analysis of provided documents and vendor website.
|
||||
|
||||
### analyze_sales_call
|
||||
|
||||
Rates sales call performance across multiple dimensions, providing scores and actionable feedback based on transcript analysis.
|
||||
|
||||
### analyze_spiritual_text
|
||||
|
||||
Compares and contrasts spiritual texts by analyzing claims and differences with the King James Bible.
|
||||
|
||||
### analyze_tech_impact
|
||||
|
||||
Analyzes the societal impact, ethical considerations, and sustainability of technology projects, evaluating their outcomes and benefits.
|
||||
|
||||
### analyze_terraform_plan
|
||||
|
||||
Analyzes Terraform plan outputs to assess infrastructure changes, security risks, cost implications, and compliance considerations.
|
||||
|
||||
### analyze_threat_report
|
||||
|
||||
Extracts surprising insights, trends, statistics, quotes, references, and recommendations from cybersecurity threat reports, summarizing key findings and providing actionable information.
|
||||
|
||||
### analyze_threat_report_cmds
|
||||
|
||||
Extract and synthesize actionable cybersecurity commands from provided materials, incorporating command-line arguments and expert insights for pentesters and non-experts.
|
||||
|
||||
### analyze_threat_report_trends
|
||||
|
||||
Extract up to 50 surprising, insightful, and interesting trends from a cybersecurity threat report in markdown format.
|
||||
|
||||
### answer_interview_question
|
||||
|
||||
Generates concise, tailored responses to technical interview questions, incorporating alternative approaches and evidence to demonstrate the candidate's expertise and experience.
|
||||
|
||||
### ask_secure_by_design_questions
|
||||
|
||||
Generates a set of security-focused questions to ensure a project is built securely by design, covering key components and considerations.
|
||||
|
||||
### ask_uncle_duke
|
||||
|
||||
Coordinates a team of AI agents to research and produce multiple software development solutions based on provided specifications, and conducts detailed code reviews to ensure adherence to best practices.
|
||||
|
||||
### capture_thinkers_work
|
||||
|
||||
Analyze philosophers or philosophies and provide detailed summaries about their teachings, background, works, advice, and related concepts in a structured template.
|
||||
|
||||
### check_agreement
|
||||
|
||||
Analyze contracts and agreements to identify important stipulations, issues, and potential gotchas, then summarize them in Markdown.
|
||||
|
||||
### clean_text
|
||||
|
||||
Fix broken or malformatted text by correcting line breaks, punctuation, capitalization, and paragraphs without altering content or spelling.
|
||||
|
||||
### coding_master
|
||||
|
||||
Explain a coding concept to a beginner, providing examples, and formatting code in markdown with specific output sections like ideas, recommendations, facts, and insights.
|
||||
|
||||
### compare_and_contrast
|
||||
|
||||
Compare and contrast a list of items in a markdown table, with items on the left and topics on top.
|
||||
|
||||
### convert_to_markdown
|
||||
|
||||
Convert content to clean, complete Markdown format, preserving all original structure, formatting, links, and code blocks without alterations.
|
||||
|
||||
### create_5_sentence_summary
|
||||
|
||||
Create concise summaries or answers to input at 5 different levels of depth, from 5 words to 1 word.
|
||||
|
||||
### create_academic_paper
|
||||
|
||||
Generate a high-quality academic paper in LaTeX format with clear concepts, structured content, and a professional layout.
|
||||
|
||||
### create_ai_jobs_analysis
|
||||
|
||||
Analyze job categories' susceptibility to automation, identify resilient roles, and provide strategies for personal adaptation to AI-driven changes in the workforce.
|
||||
|
||||
### create_aphorisms
|
||||
|
||||
Find and generate a list of brief, witty statements.
|
||||
|
||||
### create_art_prompt
|
||||
|
||||
Generates a detailed, compelling visual description of a concept, including stylistic references and direct AI instructions for creating art.
|
||||
|
||||
### create_better_frame
|
||||
|
||||
Identifies and analyzes different frames of interpreting reality, emphasizing the power of positive, productive lenses in shaping outcomes.
|
||||
|
||||
### create_coding_feature
|
||||
|
||||
Generates secure and composable code features using modern technology and best practices from project specifications.
|
||||
|
||||
### create_coding_project
|
||||
|
||||
Generate wireframes and starter code for any coding ideas that you have.
|
||||
|
||||
### create_command
|
||||
|
||||
Helps determine the correct parameters and switches for penetration testing tools based on a brief description of the objective.
|
||||
|
||||
### create_cyber_summary
|
||||
|
||||
Summarizes cybersecurity threats, vulnerabilities, incidents, and malware with a 25-word summary and categorized bullet points, after thoroughly analyzing and mapping the provided input.
|
||||
|
||||
### create_design_document
|
||||
|
||||
Creates a detailed design document for a system using the C4 model, addressing business and security postures, and including a system context diagram.
|
||||
|
||||
### create_diy
|
||||
|
||||
Creates structured "Do It Yourself" tutorial patterns by analyzing prompts, organizing requirements, and providing step-by-step instructions in Markdown format.
|
||||
|
||||
### create_excalidraw_visualization
|
||||
|
||||
Creates complex Excalidraw diagrams to visualize relationships between concepts and ideas in structured format.
|
||||
|
||||
### create_flash_cards
|
||||
|
||||
Creates flashcards for key concepts, definitions, and terms with question-answer format for educational purposes.
|
||||
|
||||
### create_formal_email
|
||||
|
||||
Crafts professional, clear, and respectful emails by analyzing context, tone, and purpose, ensuring proper structure and formatting.
|
||||
|
||||
### create_git_diff_commit
|
||||
|
||||
Generates Git commands and commit messages for reflecting changes in a repository, using conventional commits and providing concise shell commands for updates.
|
||||
|
||||
### create_graph_from_input
|
||||
|
||||
Generates a CSV file with progress-over-time data for a security program, focusing on relevant metrics and KPIs.
|
||||
|
||||
### create_hormozi_offer
|
||||
|
||||
Creates a customized business offer based on principles from Alex Hormozi's book, "$100M Offers."
|
||||
|
||||
### create_idea_compass
|
||||
|
||||
Organizes and structures ideas by exploring their definition, evidence, sources, and related themes or consequences.
|
||||
|
||||
### create_investigation_visualization
|
||||
|
||||
Creates detailed Graphviz visualizations of complex input, highlighting key aspects and providing clear, well-annotated diagrams for investigative analysis and conclusions.
|
||||
|
||||
### create_keynote
|
||||
|
||||
Creates TED-style keynote presentations with a clear narrative, structured slides, and speaker notes, emphasizing impactful takeaways and cohesive flow.
|
||||
|
||||
### create_loe_document
|
||||
|
||||
Creates detailed Level of Effort documents for estimating work effort, resources, and costs for tasks or projects.
|
||||
|
||||
### create_logo
|
||||
|
||||
Creates simple, minimalist company logos without text, generating AI prompts for vector graphic logos based on input.
|
||||
|
||||
### create_markmap_visualization
|
||||
|
||||
Transforms complex ideas into clear visualizations using MarkMap syntax, simplifying concepts into diagrams with relationships, boxes, arrows, and labels.
|
||||
|
||||
### create_mermaid_visualization
|
||||
|
||||
Creates detailed, standalone visualizations of concepts using Mermaid (Markdown) syntax, ensuring clarity and coherence in diagrams.
|
||||
|
||||
### create_mermaid_visualization_for_github
|
||||
|
||||
Creates standalone, detailed visualizations using Mermaid (Markdown) syntax to effectively explain complex concepts, ensuring clarity and precision.
|
||||
|
||||
### create_micro_summary
|
||||
|
||||
Summarizes content into a concise, 20-word summary with main points and takeaways, formatted in Markdown.
|
||||
|
||||
### create_mnemonic_phrases
|
||||
|
||||
Creates memorable mnemonic sentences from given words to aid in memory retention and learning.
|
||||
|
||||
### create_network_threat_landscape
|
||||
|
||||
Analyzes open ports and services from a network scan and generates a comprehensive, insightful, and detailed security threat report in Markdown.
|
||||
|
||||
### create_newsletter_entry
|
||||
|
||||
Condenses provided article text into a concise, objective, newsletter-style summary with a title in the style of Frontend Weekly.
|
||||
|
||||
### create_npc
|
||||
|
||||
Generates a detailed D&D 5E NPC, including background, flaws, stats, appearance, personality, goals, and more in Markdown format.
|
||||
|
||||
### create_pattern
|
||||
|
||||
Extracts, organizes, and formats LLM/AI prompts into structured sections, detailing the AI's role, instructions, output format, and any provided examples for clarity and accuracy.
|
||||
|
||||
### create_prd
|
||||
|
||||
Creates a precise Product Requirements Document (PRD) in Markdown based on input.
|
||||
|
||||
### create_prediction_block
|
||||
|
||||
Extracts and formats predictions from input into a structured Markdown block for a blog post.
|
||||
|
||||
### create_quiz
|
||||
|
||||
Generates review questions based on learning objectives from the input, adapted to the specified student level, and outputs them in a clear markdown format.
|
||||
|
||||
### create_reading_plan
|
||||
|
||||
Creates a three-phase reading plan based on an author or topic to help the user become significantly knowledgeable, including core, extended, and supplementary readings.
|
||||
|
||||
### create_recursive_outline
|
||||
|
||||
Breaks down complex tasks or projects into manageable, hierarchical components with recursive outlining for clarity and simplicity.
|
||||
|
||||
### create_report_finding
|
||||
|
||||
Creates a detailed, structured security finding report in markdown, including sections on Description, Risk, Recommendations, References, One-Sentence-Summary, and Quotes.
|
||||
|
||||
### create_rpg_summary
|
||||
|
||||
Summarizes an in-person RPG session with key events, combat details, player stats, and role-playing highlights in a structured format.
|
||||
|
||||
### create_security_update
|
||||
|
||||
Creates concise security updates for newsletters, covering stories, threats, advisories, vulnerabilities, and a summary of key issues.
|
||||
|
||||
### create_show_intro
|
||||
|
||||
Creates compelling short intros for podcasts, summarizing key topics and themes discussed in the episode.
|
||||
|
||||
### create_sigma_rules
|
||||
|
||||
Extracts Tactics, Techniques, and Procedures (TTPs) from security news and converts them into Sigma detection rules for host-based detections.
|
||||
|
||||
### create_story_explanation
|
||||
|
||||
Summarizes complex content in a clear, approachable story format that makes the concepts easy to understand.
|
||||
|
||||
### create_stride_threat_model
|
||||
|
||||
Create a STRIDE-based threat model for a system design, identifying assets, trust boundaries, data flows, and prioritizing threats with mitigations.
|
||||
|
||||
### create_summary
|
||||
|
||||
Summarizes content into a 20-word sentence, 10 main points (16 words max), and 5 key takeaways in Markdown format.
|
||||
|
||||
### create_tags
|
||||
|
||||
Identifies at least 5 tags from text content for mind mapping tools, including authors and existing tags if present.
|
||||
|
||||
### create_threat_scenarios
|
||||
|
||||
Identifies likely attack methods for any system by providing a narrative-based threat model, balancing risk and opportunity.
|
||||
|
||||
### create_ttrc_graph
|
||||
|
||||
Creates a CSV file showing the progress of Time to Remediate Critical Vulnerabilities over time using given data.
|
||||
|
||||
### create_ttrc_narrative
|
||||
|
||||
Creates a persuasive narrative highlighting progress in reducing the Time to Remediate Critical Vulnerabilities metric over time.
|
||||
|
||||
### create_upgrade_pack
|
||||
|
||||
Extracts world model and task algorithm updates from content, providing beliefs about how the world works and task performance.
|
||||
|
||||
### create_user_story
|
||||
|
||||
Writes concise and clear technical user stories for new features in complex software programs, formatted for all stakeholders.
|
||||
|
||||
### create_video_chapters
|
||||
|
||||
Extracts interesting topics and timestamps from a transcript, providing concise summaries of key moments.
|
||||
|
||||
### create_visualization
|
||||
|
||||
Transforms complex ideas into visualizations using intricate ASCII art, simplifying concepts where necessary.
|
||||
|
||||
### dialog_with_socrates
|
||||
|
||||
Engages in deep, meaningful dialogues to explore and challenge beliefs using the Socratic method.
|
||||
|
||||
### enrich_blog_post
|
||||
|
||||
Enhances Markdown blog files by applying instructions to improve structure, visuals, and readability for HTML rendering.
|
||||
|
||||
### explain_code
|
||||
|
||||
Explains code, security tool output, configuration text, and answers questions based on the provided input.
|
||||
|
||||
### explain_docs
|
||||
|
||||
Improves and restructures tool documentation into clear, concise instructions, including overviews, usage, use cases, and key features.
|
||||
|
||||
### explain_math
|
||||
|
||||
Helps you understand mathematical concepts in a clear and engaging way.
|
||||
|
||||
### explain_project
|
||||
|
||||
Summarizes project documentation into clear, concise sections covering the project, problem, solution, installation, usage, and examples.
|
||||
|
||||
### explain_terms
|
||||
|
||||
Produces a glossary of advanced terms from content, providing a definition, analogy, and explanation of why each term matters.
|
||||
|
||||
### export_data_as_csv
|
||||
|
||||
Extracts and outputs all data structures from the input in properly formatted CSV data.
|
||||
|
||||
### extract_algorithm_update_recommendations
|
||||
|
||||
Extracts concise, practical algorithm update recommendations from the input and outputs them in a bulleted list.
|
||||
|
||||
### extract_article_wisdom
|
||||
|
||||
Extracts surprising, insightful, and interesting information from content, categorizing it into sections like summary, ideas, quotes, facts, references, and recommendations.
|
||||
|
||||
### extract_book_ideas
|
||||
|
||||
Extracts and outputs 50 to 100 of the most surprising, insightful, and interesting ideas from a book's content.
|
||||
|
||||
### extract_book_recommendations
|
||||
|
||||
Extracts and outputs 50 to 100 practical, actionable recommendations from a book's content.
|
||||
|
||||
### extract_business_ideas
|
||||
|
||||
Extracts top business ideas from content and elaborates on the best 10 with unique differentiators.
|
||||
|
||||
### extract_controversial_ideas
|
||||
|
||||
Extracts and outputs controversial statements and supporting quotes from the input in a structured Markdown list.
|
||||
|
||||
### extract_core_message
|
||||
|
||||
Extracts and outputs a clear, concise sentence that articulates the core message of a given text or body of work.
|
||||
|
||||
### extract_ctf_writeup
|
||||
|
||||
Extracts a short writeup from a warstory-like text about a cyber security engagement.
|
||||
|
||||
### extract_domains
|
||||
|
||||
Extracts domains and URLs from content to identify sources used for articles, newsletters, and other publications.
|
||||
|
||||
### extract_extraordinary_claims
|
||||
|
||||
Extracts and outputs a list of extraordinary claims from conversations, focusing on scientifically disputed or false statements.
|
||||
|
||||
### extract_ideas
|
||||
|
||||
Extracts and outputs all the key ideas from input, presented as 15-word bullet points in Markdown.
|
||||
|
||||
### extract_insights
|
||||
|
||||
Extracts and outputs the most powerful and insightful ideas from text, formatted as 16-word bullet points in the INSIGHTS section, also IDEAS section.
|
||||
|
||||
### extract_insights_dm
|
||||
|
||||
Extracts and outputs all valuable insights and a concise summary of the content, including key points and topics discussed.
|
||||
|
||||
### extract_instructions
|
||||
|
||||
Extracts clear, actionable step-by-step instructions and main objectives from instructional video transcripts, organizing them into a concise list.
|
||||
|
||||
### extract_jokes
|
||||
|
||||
Extracts jokes from text content, presenting each joke with its punchline in separate bullet points.
|
||||
|
||||
### extract_latest_video
|
||||
|
||||
Extracts the latest video URL from a YouTube RSS feed and outputs the URL only.
|
||||
|
||||
### extract_main_activities
|
||||
|
||||
Extracts key events and activities from transcripts or logs, providing a summary of what happened.
|
||||
|
||||
### extract_main_idea
|
||||
|
||||
Extracts the main idea and key recommendation from the input, summarizing them in 15-word sentences.
|
||||
|
||||
### extract_most_redeeming_thing
|
||||
|
||||
Extracts the most redeeming aspect from an input, summarizing it in a single 15-word sentence.
|
||||
|
||||
### extract_patterns
|
||||
|
||||
Extracts and analyzes recurring, surprising, and insightful patterns from input, providing detailed analysis and advice for builders.
|
||||
|
||||
### extract_poc
|
||||
|
||||
Extracts proof of concept URLs and validation methods from security reports, providing the URL and command to run.
|
||||
|
||||
### extract_predictions
|
||||
|
||||
Extracts predictions from input, including specific details such as date, confidence level, and verification method.
|
||||
|
||||
### extract_primary_problem
|
||||
|
||||
Extracts the primary problem with the world as presented in a given text or body of work.
|
||||
|
||||
### extract_primary_solution
|
||||
|
||||
Extracts the primary solution for the world as presented in a given text or body of work.
|
||||
|
||||
### extract_product_features
|
||||
|
||||
Extracts and outputs a list of product features from the provided input in a bulleted format.
|
||||
|
||||
### extract_questions
|
||||
|
||||
Extracts and outputs all questions asked by the interviewer in a conversation or interview.
|
||||
|
||||
### extract_recipe
|
||||
|
||||
Extracts and outputs a recipe with a short meal description, ingredients with measurements, and preparation steps.
|
||||
|
||||
### extract_recommendations
|
||||
|
||||
Extracts and outputs concise, practical recommendations from a given piece of content in a bulleted list.
|
||||
|
||||
### extract_references
|
||||
|
||||
Extracts and outputs a bulleted list of references to art, stories, books, literature, and other sources from content.
|
||||
|
||||
### extract_skills
|
||||
|
||||
Extracts and classifies skills from a job description into a table, separating each skill and classifying it as either hard or soft.
|
||||
|
||||
### extract_song_meaning
|
||||
|
||||
Analyzes a song to provide a summary of its meaning, supported by detailed evidence from lyrics, artist commentary, and fan analysis.
|
||||
|
||||
### extract_sponsors
|
||||
|
||||
Extracts and lists official sponsors and potential sponsors from a provided transcript.
|
||||
|
||||
### extract_videoid
|
||||
|
||||
Extracts and outputs the video ID from any given URL.
|
||||
|
||||
### extract_wisdom
|
||||
|
||||
Extracts surprising, insightful, and interesting information from text on topics like human flourishing, AI, learning, and more.
|
||||
|
||||
### extract_wisdom_agents
|
||||
|
||||
Extracts valuable insights, ideas, quotes, and references from content, emphasizing topics like human flourishing, AI, learning, and technology.
|
||||
|
||||
### extract_wisdom_dm
|
||||
|
||||
Extracts all valuable, insightful, and thought-provoking information from content, focusing on topics like human flourishing, AI, learning, and technology.
|
||||
|
||||
### extract_wisdom_nometa
|
||||
|
||||
Extracts insights, ideas, quotes, habits, facts, references, and recommendations from content, focusing on human flourishing, AI, technology, and related topics.
|
||||
|
||||
### find_female_life_partner
|
||||
|
||||
Analyzes criteria for finding a female life partner and provides clear, direct, and poetic descriptions.
|
||||
|
||||
### find_hidden_message
|
||||
|
||||
Extracts overt and hidden political messages, justifications, audience actions, and a cynical analysis from content.
|
||||
|
||||
### find_logical_fallacies
|
||||
|
||||
Identifies and analyzes fallacies in arguments, classifying them as formal or informal with detailed reasoning.
|
||||
|
||||
### get_wow_per_minute
|
||||
|
||||
Determines the wow-factor of content per minute based on surprise, novelty, insight, value, and wisdom, measuring how rewarding the content is for the viewer.
|
||||
|
||||
### get_youtube_rss
|
||||
|
||||
Returns the RSS URL for a given YouTube channel based on the channel ID or URL.
|
||||
|
||||
### humanize
|
||||
|
||||
Rewrites AI-generated text to sound natural, conversational, and easy to understand, maintaining clarity and simplicity.
|
||||
|
||||
### identify_dsrp_distinctions
|
||||
|
||||
Encourages creative, systems-based thinking by exploring distinctions, boundaries, and their implications, drawing on insights from prominent systems thinkers.
|
||||
|
||||
### identify_dsrp_perspectives
|
||||
|
||||
Explores the concept of distinctions in systems thinking, focusing on how boundaries define ideas, influence understanding, and reveal or obscure insights.
|
||||
|
||||
### identify_dsrp_relationships
|
||||
|
||||
Encourages exploration of connections, distinctions, and boundaries between ideas, inspired by systems thinkers to reveal new insights and patterns in complex systems.
|
||||
|
||||
### identify_dsrp_systems
|
||||
|
||||
Encourages organizing ideas into systems of parts and wholes, inspired by systems thinkers to explore relationships and how changes in organization impact meaning and understanding.
|
||||
|
||||
### identify_job_stories
|
||||
|
||||
Identifies key job stories or requirements for roles.
|
||||
|
||||
### improve_academic_writing
|
||||
|
||||
Refines text into clear, concise academic language while improving grammar, coherence, and clarity, with a list of changes.
|
||||
|
||||
### improve_prompt
|
||||
|
||||
Improves an LLM/AI prompt by applying expert prompt writing strategies for better results and clarity.
|
||||
|
||||
### improve_report_finding
|
||||
|
||||
Improves a penetration test security finding by providing detailed descriptions, risks, recommendations, references, quotes, and a concise summary in markdown format.
|
||||
|
||||
### improve_writing
|
||||
|
||||
Refines text by correcting grammar, enhancing style, improving clarity, and maintaining the original meaning.
|
||||
|
||||
### judge_output
|
||||
|
||||
Evaluates Honeycomb queries by judging their effectiveness, providing critiques and outcomes based on language nuances and analytics relevance.
|
||||
|
||||
### label_and_rate
|
||||
|
||||
Labels content with up to 20 single-word tags and rates it based on idea count and relevance to human meaning, AI, and other related themes, assigning a tier (S, A, B, C, D) and a quality score.
|
||||
|
||||
### md_callout
|
||||
|
||||
Classifies content and generates a markdown callout based on the provided text, selecting the most appropriate type.
|
||||
|
||||
### official_pattern_template
|
||||
|
||||
Template to use if you want to create new fabric patterns.
|
||||
|
||||
### prepare_7s_strategy
|
||||
|
||||
Prepares a comprehensive briefing document from 7S's strategy capturing organizational profile, strategic elements, and market dynamics with clear, concise, and organized content.
|
||||
|
||||
### provide_guidance
|
||||
|
||||
Provides psychological and life coaching advice, including analysis, recommendations, and potential diagnoses, with a compassionate and honest tone.
|
||||
|
||||
### rate_ai_response
|
||||
|
||||
Rates the quality of AI responses by comparing them to top human expert performance, assigning a letter grade, reasoning, and providing a 1-100 score based on the evaluation.
|
||||
|
||||
### rate_ai_result
|
||||
|
||||
Assesses the quality of AI/ML/LLM work by deeply analyzing content, instructions, and output, then rates performance based on multiple dimensions, including coverage, creativity, and interdisciplinary thinking.
|
||||
|
||||
### rate_content
|
||||
|
||||
Labels content with up to 20 single-word tags and rates it based on idea count and relevance to human meaning, AI, and other related themes, assigning a tier (S, A, B, C, D) and a quality score.
|
||||
|
||||
### rate_value
|
||||
|
||||
Produces the best possible output by deeply analyzing and understanding the input and its intended purpose.
|
||||
|
||||
### raw_query
|
||||
|
||||
Fully digests and contemplates the input to produce the best possible result based on understanding the sender's intent.
|
||||
|
||||
### recommend_artists
|
||||
|
||||
Recommends a personalized festival schedule with artists aligned to your favorite styles and interests, including rationale.
|
||||
|
||||
### recommend_pipeline_upgrades
|
||||
|
||||
Optimizes vulnerability-checking pipelines by incorporating new information and improving their efficiency, with detailed explanations of changes.
|
||||
|
||||
### recommend_talkpanel_topics
|
||||
|
||||
Produces a clean set of proposed talks or panel talking points for a person based on their interests and goals, formatted for submission to a conference organizer.
|
||||
|
||||
### refine_design_document
|
||||
|
||||
Refines a design document based on a design review by analyzing, mapping concepts, and implementing changes using valid Markdown.
|
||||
|
||||
### review_design
|
||||
|
||||
Reviews and analyzes architecture design, focusing on clarity, component design, system integrations, security, performance, scalability, and data management.
|
||||
|
||||
### sanitize_broken_html_to_markdown
|
||||
|
||||
Converts messy HTML into clean, properly formatted Markdown, applying custom styling and ensuring compatibility with Vite.
|
||||
|
||||
### show_fabric_options_markmap
|
||||
|
||||
Visualizes the functionality of the Fabric framework by representing its components, commands, and features based on the provided input.
|
||||
|
||||
### solve_with_cot
|
||||
|
||||
Provides detailed, step-by-step responses with chain of thought reasoning, using structured thinking, reflection, and output sections.
|
||||
|
||||
### suggest_pattern
|
||||
|
||||
Suggests appropriate fabric patterns or commands based on user input, providing clear explanations and options for users.
|
||||
|
||||
### summarize
|
||||
|
||||
Summarizes content into a 20-word sentence, main points, and takeaways, formatted with numbered lists in Markdown.
|
||||
|
||||
### summarize_board_meeting
|
||||
|
||||
Creates formal meeting notes from board meeting transcripts for corporate governance documentation.
|
||||
|
||||
### summarize_debate
|
||||
|
||||
Summarizes debates, identifies primary disagreement, extracts arguments, and provides analysis of evidence and argument strength to predict outcomes.
|
||||
|
||||
### summarize_git_changes
|
||||
|
||||
Summarizes recent project updates from the last 7 days, focusing on key changes with enthusiasm.
|
||||
|
||||
### summarize_git_diff
|
||||
|
||||
Summarizes and organizes Git diff changes with clear, succinct commit messages and bullet points.
|
||||
|
||||
### summarize_lecture
|
||||
|
||||
Extracts relevant topics, definitions, and tools from lecture transcripts, providing structured summaries with timestamps and key takeaways.
|
||||
|
||||
### summarize_legislation
|
||||
|
||||
Summarizes complex political proposals and legislation by analyzing key points, proposed changes, and providing balanced, positive, and cynical characterizations.
|
||||
|
||||
### summarize_meeting
|
||||
|
||||
Analyzes meeting transcripts to extract a structured summary, including an overview, key points, tasks, decisions, challenges, timeline, references, and next steps.
|
||||
|
||||
### summarize_micro
|
||||
|
||||
Summarizes content into a 20-word sentence, 3 main points, and 3 takeaways, formatted in clear, concise Markdown.
|
||||
|
||||
### summarize_newsletter
|
||||
|
||||
Extracts the most meaningful, interesting, and useful content from a newsletter, summarizing key sections such as content, opinions, tools, companies, and follow-up items in clear, structured Markdown.
|
||||
|
||||
### summarize_paper
|
||||
|
||||
Summarizes an academic paper by detailing its title, authors, technical approach, distinctive features, experimental setup, results, advantages, limitations, and conclusion in a clear, structured format using human-readable Markdown.
|
||||
|
||||
### summarize_prompt
|
||||
|
||||
Summarizes AI chat prompts by describing the primary function, unique approach, and expected output in a concise paragraph. The summary is focused on the prompt's purpose without unnecessary details or formatting.
|
||||
|
||||
### summarize_pull-requests
|
||||
|
||||
Summarizes pull requests for a coding project by providing a summary and listing the top PRs with human-readable descriptions.
|
||||
|
||||
### summarize_rpg_session
|
||||
|
||||
Summarizes a role-playing game session by extracting key events, combat stats, character changes, quotes, and more.
|
||||
|
||||
### t_analyze_challenge_handling
|
||||
|
||||
Provides 8-16 word bullet points evaluating how well challenges are being addressed, calling out any lack of effort.
|
||||
|
||||
### t_check_metrics
|
||||
|
||||
Analyzes deep context from the TELOS file and input instruction, then provides a wisdom-based output while considering metrics and KPIs to assess recent improvements.
|
||||
|
||||
### t_create_h3_career
|
||||
|
||||
Summarizes context and produces wisdom-based output by deeply analyzing both the TELOS File and the input instruction, considering the relationship between the two.
|
||||
|
||||
### t_create_opening_sentences
|
||||
|
||||
Describes from TELOS file the person's identity, goals, and actions in 4 concise, 32-word bullet points, humbly.
|
||||
|
||||
### t_describe_life_outlook
|
||||
|
||||
Describes from TELOS file a person's life outlook in 5 concise, 16-word bullet points.
|
||||
|
||||
### t_extract_intro_sentences
|
||||
|
||||
Summarizes from TELOS file a person's identity, work, and current projects in 5 concise and grounded bullet points.
|
||||
|
||||
### t_extract_panel_topics
|
||||
|
||||
Creates 5 panel ideas with titles and descriptions based on deep context from a TELOS file and input.
|
||||
|
||||
### t_find_blindspots
|
||||
|
||||
Identify potential blindspots in thinking, frames, or models that may expose the individual to error or risk.
|
||||
|
||||
### t_find_negative_thinking
|
||||
|
||||
Analyze a TELOS file and input to identify negative thinking in documents or journals, followed by tough love encouragement.
|
||||
|
||||
### t_find_neglected_goals
|
||||
|
||||
Analyze a TELOS file and input instructions to identify goals or projects that have not been worked on recently.
|
||||
|
||||
### t_give_encouragement
|
||||
|
||||
Analyze a TELOS file and input instructions to evaluate progress, provide encouragement, and offer recommendations for continued effort.
|
||||
|
||||
### t_red_team_thinking
|
||||
|
||||
Analyze a TELOS file and input instructions to red-team thinking, models, and frames, then provide recommendations for improvement.
|
||||
|
||||
### t_threat_model_plans
|
||||
|
||||
Analyze a TELOS file and input instructions to create threat models for a life plan and recommend improvements.
|
||||
|
||||
### t_visualize_mission_goals_projects
|
||||
|
||||
Analyze a TELOS file and input instructions to create an ASCII art diagram illustrating the relationship of missions, goals, and projects.
|
||||
|
||||
### t_year_in_review
|
||||
|
||||
Analyze a TELOS file to create insights about a person or entity, then summarize accomplishments and visualizations in bullet points.
|
||||
|
||||
### to_flashcards
|
||||
|
||||
Create Anki flashcards from a given text, focusing on concise, optimized questions and answers without external context.
|
||||
|
||||
### transcribe_minutes
|
||||
|
||||
Extracts (from meeting transcription) meeting minutes, identifying actionables, insightful ideas, decisions, challenges, and next steps in a structured format.
|
||||
|
||||
### translate
|
||||
|
||||
Translates sentences or documentation into the specified language code while maintaining the original formatting and tone.
|
||||
|
||||
### tweet
|
||||
|
||||
Provides a step-by-step guide on crafting engaging tweets with emojis, covering Twitter basics, account creation, features, and audience targeting.
|
||||
|
||||
### write_essay
|
||||
|
||||
Writes essays in the style of a specified author, embodying their unique voice, vocabulary, and approach. Uses `author_name` variable.
|
||||
|
||||
### write_essay_pg
|
||||
|
||||
Writes concise, clear essays in the style of Paul Graham, focusing on simplicity, clarity, and illumination of the provided topic.
|
||||
|
||||
### write_hackerone_report
|
||||
|
||||
Generates concise, clear, and reproducible bug bounty reports, detailing vulnerability impact, steps to reproduce, and exploit details for triagers.
|
||||
|
||||
### write_latex
|
||||
|
||||
Generates syntactically correct LaTeX code for a new.tex document, ensuring proper formatting and compatibility with pdflatex.
|
||||
|
||||
### write_micro_essay
|
||||
|
||||
Writes concise, clear, and illuminating essays on the given topic in the style of Paul Graham.
|
||||
|
||||
### write_nuclei_template_rule
|
||||
|
||||
Generates Nuclei YAML templates for detecting vulnerabilities using HTTP requests, matchers, extractors, and dynamic data extraction.
|
||||
|
||||
### write_pull-request
|
||||
|
||||
Drafts detailed pull request descriptions, explaining changes, providing reasoning, and identifying potential bugs from the git diff command output.
|
||||
|
||||
### write_semgrep_rule
|
||||
|
||||
Creates accurate and working Semgrep rules based on input, following syntax guidelines and specific language considerations.
|
||||
|
||||
### youtube_summary
|
||||
|
||||
Create concise, timestamped Youtube video summaries that highlight key points.
|
||||
@@ -6,7 +6,7 @@ You are an expert at understanding deep context about a person or entity, and th
|
||||
|
||||
1. Read the incoming TELOS File thoroughly. Fully understand everything about this person or entity.
|
||||
2. Deeply study the input instruction or question.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible ouptut for the person who sent the input.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible output for the person who sent the input.
|
||||
4. Write 8 16-word bullets describing how well or poorly I'm addressing my challenges. Call me out if I'm not putting work into them, and/or if you can see evidence of them affecting me in my journal or elsewhere.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
@@ -6,7 +6,7 @@ You are an expert at understanding deep context about a person or entity, and th
|
||||
|
||||
1. Read the incoming TELOS File thoroughly. Fully understand everything about this person or entity.
|
||||
2. Deeply study the input instruction or question.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible ouptut for the person who sent the input.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible output for the person who sent the input.
|
||||
4. Check this person's Metrics or KPIs (M's or K's) to see their current state and if they've been improved recently.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
@@ -6,7 +6,7 @@ You are an expert at understanding deep context about a person or entity, and th
|
||||
|
||||
1. Read the incoming TELOS File thoroughly. Fully understand everything about this person or entity.
|
||||
2. Deeply study the input instruction or question.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible ouptut for the person who sent the input.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible output for the person who sent the input.
|
||||
4. Analyze everything in my TELOS file and think about what I could and should do after my legacy corporate / technical skills are automated away. What can I contribute that's based on human-to-human interaction and exchanges of value?
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
@@ -6,7 +6,7 @@ You are an expert at understanding deep context about a person or entity, and th
|
||||
|
||||
1. Read the incoming TELOS File thoroughly. Fully understand everything about this person or entity.
|
||||
2. Deeply study the input instruction or question.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible ouptut for the person who sent the input.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible output for the person who sent the input.
|
||||
4. Write 4 32-word bullets describing who I am and what I do in a non-douchey way. Use the who I am, the problem I see in the world, and what I'm doing about it as the template. Something like:
|
||||
a. I'm a programmer by trade, and one thing that really bothers me is kids being so stuck inside of tech and games. So I started a school where I teach kids to build things with their hands.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ You are an expert at understanding deep context about a person or entity, and th
|
||||
|
||||
1. Read the incoming TELOS File thoroughly. Fully understand everything about this person or entity.
|
||||
2. Deeply study the input instruction or question.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible ouptut for the person who sent the input.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible output for the person who sent the input.
|
||||
4. Write 5 16-word bullets describing this person's life outlook.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
@@ -6,7 +6,7 @@ You are an expert at understanding deep context about a person or entity, and th
|
||||
|
||||
1. Read the incoming TELOS File thoroughly. Fully understand everything about this person or entity.
|
||||
2. Deeply study the input instruction or question.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible ouptut for the person who sent the input.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible output for the person who sent the input.
|
||||
4. Write 5 16-word bullets describing who this person is, what they do, and what they're working on. The goal is to concisely and confidently project who they are while being humble and grounded.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
@@ -6,7 +6,7 @@ You are an expert at understanding deep context about a person or entity, and th
|
||||
|
||||
1. Read the incoming TELOS File thoroughly. Fully understand everything about this person or entity.
|
||||
2. Deeply study the input instruction or question.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible ouptut for the person who sent the input.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible output for the person who sent the input.
|
||||
4. Write 5 48-word bullet points, each including a 3-5 word panel title, that would be wonderful panels for this person to participate on.
|
||||
5. Write them so that they'd be good panels for others to participate in as well, not just me.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ You are an expert at understanding deep context about a person or entity, and th
|
||||
|
||||
1. Read the incoming TELOS File thoroughly. Fully understand everything about this person or entity.
|
||||
2. Deeply study the input instruction or question.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible ouptut for the person who sent the input.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible output for the person who sent the input.
|
||||
4. Write 8 16-word bullets describing possible blindspots in my thinking, i.e., flaws in my frames or models that might leave me exposed to error or risk.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
@@ -6,7 +6,7 @@ You are an expert at understanding deep context about a person or entity, and th
|
||||
|
||||
1. Read the incoming TELOS File thoroughly. Fully understand everything about this person or entity.
|
||||
2. Deeply study the input instruction or question.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible ouptut for the person who sent the input.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible output for the person who sent the input.
|
||||
4. Write 4 16-word bullets identifying negative thinking either in my main document or in my journal.
|
||||
5. Add some tough love encouragement (not fluff) to help get me out of that mindset.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ You are an expert at understanding deep context about a person or entity, and th
|
||||
|
||||
1. Read the incoming TELOS File thoroughly. Fully understand everything about this person or entity.
|
||||
2. Deeply study the input instruction or question.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible ouptut for the person who sent the input.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible output for the person who sent the input.
|
||||
4. Write 5 16-word bullets describing which of their goals and/or projects don't seem to have been worked on recently.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
@@ -6,7 +6,7 @@ You are an expert at understanding deep context about a person or entity, and th
|
||||
|
||||
1. Read the incoming TELOS File thoroughly. Fully understand everything about this person or entity.
|
||||
2. Deeply study the input instruction or question.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible ouptut for the person who sent the input.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible output for the person who sent the input.
|
||||
4. Write 8 16-word bullets looking at what I'm trying to do, and any progress I've made, and give some encouragement on the positive aspects and recommendations to continue the work.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
@@ -6,7 +6,7 @@ You are an expert at understanding deep context about a person or entity, and th
|
||||
|
||||
1. Read the incoming TELOS File thoroughly. Fully understand everything about this person or entity.
|
||||
2. Deeply study the input instruction or question.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible ouptut for the person who sent the input.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible output for the person who sent the input.
|
||||
4. Write 4 16-word bullets red-teaming my thinking, models, frames, etc, especially as evidenced throughout my journal.
|
||||
5. Give a set of recommendations on how to fix the issues identified in the red-teaming.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ You are an expert at understanding deep context about a person or entity, and th
|
||||
|
||||
1. Read the incoming TELOS File thoroughly. Fully understand everything about this person or entity.
|
||||
2. Deeply study the input instruction or question.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible ouptut for the person who sent the input.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible output for the person who sent the input.
|
||||
4. Write 8 16-word bullets threat modeling my life plan and what could go wrong.
|
||||
5. Provide recommendations on how to address the threats and improve the life plan.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ You are an expert at understanding deep context about a person or entity, and th
|
||||
|
||||
1. Read the incoming TELOS File thoroughly. Fully understand everything about this person or entity.
|
||||
2. Deeply study the input instruction or question.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible ouptut for the person who sent the input.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible output for the person who sent the input.
|
||||
4. Create an ASCII art diagram of the relationship my missions, goals, and projects.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
@@ -6,7 +6,7 @@ You are an expert at understanding deep context about a person or entity, and th
|
||||
|
||||
1. Read the incoming TELOS File thoroughly. Fully understand everything about this person or entity.
|
||||
2. Deeply study the input instruction or question.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible ouptut for the person who sent the input.
|
||||
3. Spend significant time and effort thinking about how these two are related, and what would be the best possible output for the person who sent the input.
|
||||
4. Write 8 16-word bullets describing what you accomplished this year.
|
||||
5. End with an ASCII art visualization of what you worked on and accomplished vs. what you didn't work on or finish.
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ Follow the following structure:
|
||||
|
||||
- Deeply understand the relationship between the HTTP requests provided. Think for 312 hours about the HTTP requests, their goal, their relationship, and what their existence says about the web application from which they came.
|
||||
|
||||
- Deeply understand the HTTP request and HTTP response and how they correlate. Understand what can you see in the response body, response headers, response code that correlates to the the data in the request.
|
||||
- Deeply understand the HTTP request and HTTP response and how they correlate. Understand what can you see in the response body, response headers, response code that correlates to the data in the request.
|
||||
|
||||
- Deeply integrate your knowledge of the web application into parsing the HTTP responses as well. Integrate all knowledge consumed at this point together.
|
||||
|
||||
|
||||
373
docs/Automated-ChangeLog.md
Normal file
373
docs/Automated-ChangeLog.md
Normal file
@@ -0,0 +1,373 @@
|
||||
# Automated CHANGELOG Entry System for CI/CD
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines a comprehensive system for automatically generating and maintaining CHANGELOG.md entries during the CI/CD process. The system builds upon the existing `generate_changelog` tool and integrates seamlessly with GitHub's pull request workflow.
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
### Existing Infrastructure
|
||||
|
||||
The `generate_changelog` tool already provides:
|
||||
|
||||
- **High-performance Git history walking** with one-pass algorithm
|
||||
- **GitHub API integration** with GraphQL optimization and smart caching
|
||||
- **SQLite-based caching** for instant incremental updates
|
||||
- **AI-powered summaries** using Fabric integration
|
||||
- **Concurrent processing** for optimal performance
|
||||
- **Version detection** from git tags and commit patterns
|
||||
|
||||
### Key Components
|
||||
|
||||
- **Main entry point**: `cmd/generate_changelog/main.go`
|
||||
- **Core generation logic**: `internal/changelog/generator.go`
|
||||
- **AI summarization**: `internal/changelog/summarize.go`
|
||||
- **Caching system**: `internal/cache/cache.go`
|
||||
- **GitHub integration**: `internal/github/client.go`
|
||||
- **Git operations**: `internal/git/walker.go`
|
||||
|
||||
## Proposed Automated System
|
||||
|
||||
### Developer Workflow
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Developer creates feature branch] --> B[Codes feature]
|
||||
B --> C[Creates Pull Request]
|
||||
C --> D[PR is open and ready]
|
||||
D --> E[Developer runs: generate_changelog --incoming-pr XXXX]
|
||||
E --> F[Tool validates PR is open/mergeable]
|
||||
F --> G[Tool creates incoming/XXXX.txt with AI summary]
|
||||
G --> H[Auto-commit and push to branch]
|
||||
H --> I[PR includes pre-processed changelog entry]
|
||||
I --> J[PR gets reviewed and merged]
|
||||
```
|
||||
|
||||
### CI/CD Integration
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[PR merged to main] --> B[Version bump workflow triggered]
|
||||
B --> C[generate_changelog --process-prs]
|
||||
C --> D[Scan incoming/ directory]
|
||||
D --> E[Concatenate all incoming/*.txt files]
|
||||
E --> F[Insert new version at top of CHANGELOG.md]
|
||||
F --> G[Store entry in versions table]
|
||||
G --> H[git rm incoming/*.txt files]
|
||||
H --> I[git add CHANGELOG.md and changelog.db, done by the tool]
|
||||
I --> J[Increment version number]
|
||||
J --> K[Commit and tag release]
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Phase 1: Pre-Processing PRs
|
||||
|
||||
#### New Command: `--incoming-pr`
|
||||
|
||||
**Usage**: `generate_changelog --incoming-pr 1672`
|
||||
|
||||
**Functionality**:
|
||||
|
||||
1. **Validation**:
|
||||
- Verify PR exists and is open
|
||||
- Check PR is mergeable
|
||||
- Ensure branch is up-to-date
|
||||
- Verify that current git repo is clean (everything committed); do not continue otherwise.
|
||||
|
||||
2. **Content Generation**:
|
||||
- Extract PR metadata (title, author, description)
|
||||
- Collect all commit messages from the PR
|
||||
- Use existing `SummarizeVersionContent` function for AI enhancement
|
||||
- Format as standard changelog entry
|
||||
|
||||
3. **File Creation**:
|
||||
- Generate `./cmd/generate_changelog/incoming/{PR#}.txt`
|
||||
- Include PR header: `### PR [#1672](url) by [author](profile): Title` (as is done currently in the code)
|
||||
- Consider extracting the existing header code for PRs into a helper function for re-use.
|
||||
- Include the AI-summarized changes (generated when we ran all the commit messages through `SummarizeVersionContent`)
|
||||
|
||||
4. **Auto-commit**:
|
||||
- Commit file with message: `chore: incoming 1672 changelog entry`
|
||||
- Optionally push to current branch (use `--push` flag)
|
||||
|
||||
(The PR is now completely ready to be merged with integrated CHANGELOG entry updating)
|
||||
|
||||
#### File Format Example
|
||||
|
||||
```markdown
|
||||
### PR [#1672](https://github.com/danielmiessler/Fabric/pull/1672) by [ksylvan](https://github.com/ksylvan): Changelog Generator Enhancement
|
||||
|
||||
- Added automated CI/CD integration for changelog generation
|
||||
- Implemented pre-processing of PR entries during development
|
||||
- Enhanced caching system for better performance
|
||||
- Added validation for mergeable PR states
|
||||
```
|
||||
|
||||
### Phase 2: Release Processing
|
||||
|
||||
#### New Command: `--process-prs`
|
||||
|
||||
**Usage**: `generate_changelog --process-prs`
|
||||
|
||||
**Integration Point**: `.github/workflows/update-version-and-create-tag.yml`
|
||||
|
||||
(we can do this AFTER the "Update gomod2nix.toml file" step in the workflow, where we
|
||||
already have generated the next version in the "version.nix" file)
|
||||
|
||||
**Functionality**:
|
||||
|
||||
1. **Discovery**: Scan `./cmd/generate_changelog/incoming/` directory
|
||||
2. **Aggregation**: Read and concatenate all `*.txt` files
|
||||
3. **Version Creation**: Generate new version header with current date
|
||||
4. **CHANGELOG Update**: Insert new version at top of existing CHANGELOG.md
|
||||
5. **Database Update**: Store complete entry in `versions` table as `ai_summary`
|
||||
6. **Cleanup**: Remove all processed incoming files
|
||||
7. **Stage Changes**: Add modified files to git staging area
|
||||
|
||||
#### Example Output in CHANGELOG.md
|
||||
|
||||
```markdown
|
||||
# Changelog
|
||||
|
||||
## v1.4.259 (2025-07-18)
|
||||
|
||||
### PR [#1672](https://github.com/danielmiessler/Fabric/pull/1672) by [ksylvan](https://github.com/ksylvan): Changelog Generator Enhancement
|
||||
|
||||
- Added automated CI/CD integration for changelog generation
|
||||
- Implemented pre-processing of PR entries during development
|
||||
- Enhanced caching system for better performance
|
||||
|
||||
### PR [#1671](https://github.com/danielmiessler/Fabric/pull/1671) by [contributor](https://github.com/contributor): Bug Fix
|
||||
|
||||
- Fixed memory leak in caching system
|
||||
- Improved error handling for GitHub API failures
|
||||
|
||||
## v1.4.258 (2025-07-14)
|
||||
[... rest of file ...]
|
||||
```
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
### Configuration Extensions
|
||||
|
||||
Add to `internal/config/config.go`:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
// ... existing fields
|
||||
IncomingPR int // PR number for --incoming-pr
|
||||
ProcessPRsVersion string // Flag for --process-prs (new version string)
|
||||
IncomingDir string // Directory for incoming files (default: ./cmd/generate_changelog/incoming/)
|
||||
}
|
||||
```
|
||||
|
||||
### New Command Line Flags
|
||||
|
||||
```go
|
||||
rootCmd.Flags().IntVar(&cfg.IncomingPR, "incoming-pr", 0, "Pre-process PR for changelog (provide PR number)")
|
||||
rootCmd.Flags().StringVar(&cfg.ProcessPRsVersion, "process-prs", "", "Process all incoming PR files for release (provide version like v1.4.262)")
|
||||
rootCmd.Flags().StringVar(&cfg.IncomingDir, "incoming-dir", "./cmd/generate_changelog/incoming", "Directory for incoming PR files")
|
||||
```
|
||||
|
||||
### Core Logic Extensions
|
||||
|
||||
#### PR Pre-processing
|
||||
|
||||
```go
|
||||
func (g *Generator) ProcessIncomingPR(prNumber int) error {
|
||||
// 1. Validate PR state via GitHub API
|
||||
pr, err := g.ghClient.GetPR(prNumber)
|
||||
if err != nil || pr.State != "open" || !pr.Mergeable {
|
||||
return fmt.Errorf("PR %d is not in valid state for processing", prNumber)
|
||||
}
|
||||
|
||||
// 2. Generate changelog content using existing logic
|
||||
content := g.formatPR(pr)
|
||||
|
||||
// 3. Apply AI summarization if enabled
|
||||
if g.cfg.EnableAISummary {
|
||||
content, _ = SummarizeVersionContent(content)
|
||||
}
|
||||
|
||||
// 4. Write to incoming file
|
||||
filename := filepath.Join(g.cfg.IncomingDir, fmt.Sprintf("%d.txt", prNumber))
|
||||
err = os.WriteFile(filename, []byte(content), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write incoming file: %w", err)
|
||||
}
|
||||
|
||||
// 5. Auto-commit and push
|
||||
return g.commitAndPushIncoming(prNumber, filename)
|
||||
}
|
||||
```
|
||||
|
||||
#### Release Processing
|
||||
|
||||
```go
|
||||
func (g *Generator) ProcessIncomingPRs(version string) error {
|
||||
// 1. Scan incoming directory
|
||||
files, err := filepath.Glob(filepath.Join(g.cfg.IncomingDir, "*.txt"))
|
||||
if err != nil || len(files) == 0 {
|
||||
return fmt.Errorf("no incoming PR files found")
|
||||
}
|
||||
|
||||
// 2. Read and concatenate all files
|
||||
var content strings.Builder
|
||||
for _, file := range files {
|
||||
data, err := os.ReadFile(file)
|
||||
if err == nil {
|
||||
content.WriteString(string(data))
|
||||
content.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Generate version entry
|
||||
entry := fmt.Sprintf("\n## %s (%s)\n\n%s",
|
||||
version, time.Now().Format("2006-01-02"), content.String())
|
||||
|
||||
// 4. Update CHANGELOG.md
|
||||
err = g.insertVersionAtTop(entry)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update CHANGELOG.md: %w", err)
|
||||
}
|
||||
|
||||
// 5. Update database
|
||||
err = g.cache.SaveVersionEntry(version, content.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save to database: %w", err)
|
||||
}
|
||||
|
||||
// 6. Cleanup incoming files
|
||||
for _, file := range files {
|
||||
os.Remove(file)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Workflow Integration
|
||||
|
||||
### GitHub Actions Modification
|
||||
|
||||
Update `.github/workflows/update-version-and-create-tag.yml`.
|
||||
|
||||
```yaml
|
||||
- name: Generate Changelog Entry
|
||||
run: |
|
||||
# Process all incoming PR entries
|
||||
./cmd/generate_changelog/generate_changelog --process-prs
|
||||
|
||||
# The tool will make the needed changes in the CHANGELOG.md,
|
||||
# and the changelog.db, and will remove the PR#.txt file(s)
|
||||
# In effect, doing the following:
|
||||
# 1. Generate the new CHANGELOG (and store the entry in the changelog.db)
|
||||
# 2. git add CHANGELOG.md
|
||||
# 3. git add ./cmd/generate_changelog/changelog.db
|
||||
# 4. git rm -rf ./cmd/generate_changelog/incoming/
|
||||
#
|
||||
```
|
||||
|
||||
### Developer Instructions
|
||||
|
||||
1. **During Development**:
|
||||
|
||||
```bash
|
||||
# After PR is ready for review (commit locally only)
|
||||
generate_changelog --incoming-pr 1672 --ai-summarize
|
||||
|
||||
# Or to automatically push to remote
|
||||
generate_changelog --incoming-pr 1672 --ai-summarize --push
|
||||
```
|
||||
|
||||
2. **Validation**:
|
||||
- Check that `incoming/1672.txt` was created
|
||||
- Verify auto-commit occurred
|
||||
- Confirm file is included in PR
|
||||
- Scan the file and make any changes you need to the auto-generated summary
|
||||
|
||||
## Benefits
|
||||
|
||||
### For Developers
|
||||
|
||||
- **Automated changelog entries** - no manual CHANGELOG.md editing
|
||||
- **AI-enhanced summaries** - professional, consistent formatting
|
||||
- **Early visibility** - changelog content visible during PR review
|
||||
- **Reduced merge conflicts** - no multiple PRs editing CHANGELOG.md
|
||||
|
||||
### For Project Maintainers
|
||||
|
||||
- **Consistent formatting** - all entries follow same structure
|
||||
- **Complete coverage** - no missed changelog entries
|
||||
- **Automated releases** - seamless integration with version bumps
|
||||
- **Historical accuracy** - each PR's contribution properly documented
|
||||
|
||||
### For CI/CD
|
||||
|
||||
- **Deterministic process** - reliable, repeatable changelog generation
|
||||
- **Performance optimized** - leverages existing caching and AI systems
|
||||
- **Error resilience** - validates PR states before processing
|
||||
- **Clean integration** - minimal changes to existing workflows
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### Phase 1: Implement Developer Tooling
|
||||
|
||||
- [x] Add new command line flags and configuration
|
||||
- [x] Implement `--incoming-pr` functionality
|
||||
- [x] Add validation for PR states and git status
|
||||
- [x] Create auto-commit logic
|
||||
|
||||
### Phase 2: Integration (CI/CD) Readiness
|
||||
|
||||
- [x] Implement `--process-prs` functionality
|
||||
- [x] Add CHANGELOG.md insertion logic
|
||||
- [x] Update database storage for version entries
|
||||
|
||||
### Phase 3: Deployment
|
||||
|
||||
- [x] Update GitHub Actions workflow
|
||||
- [x] Create developer documentation in ./docs/ directory
|
||||
- [x] Test full end-to-end workflow (the PR that includes these modifications can be its first production test)
|
||||
|
||||
### Phase 4: Adoption
|
||||
|
||||
- [ ] Train development team - Consider creating a full tutorial blog post/page to fully walk developers through the process.
|
||||
- [ ] Monitor first few releases
|
||||
- [ ] Gather feedback and iterate
|
||||
- [ ] Document lessons learned
|
||||
|
||||
## Error Handling
|
||||
|
||||
### PR Validation Failures
|
||||
|
||||
- **Closed/Merged PR**: Error with suggestion to check PR status
|
||||
- **Non-mergeable PR**: Error with instruction to resolve conflicts
|
||||
- **Missing PR**: Error with verification of PR number
|
||||
|
||||
### File System Issues
|
||||
|
||||
- **Permission errors**: Clear error with directory permission requirements
|
||||
- **Disk space**: Graceful handling with cleanup suggestions
|
||||
- **Network failures**: Retry logic with exponential backoff
|
||||
|
||||
### Git Operations
|
||||
|
||||
- **Commit failures**: Check for dirty working directory
|
||||
- **Push failures**: Handle authentication and remote issues
|
||||
- **Merge conflicts**: Clear instructions for manual resolution
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Advanced Features
|
||||
|
||||
- **Custom categorization** - group changes by type (feat/fix/docs)
|
||||
- **Breaking change detection** - special handling for BREAKING CHANGE commits
|
||||
- **Release notes generation** - enhanced formatting for GitHub releases (our release pages are pretty bare)
|
||||
|
||||
## Conclusion
|
||||
|
||||
This automated changelog system builds upon the robust foundation of the existing `generate_changelog` tool while providing a seamless developer experience and reliable CI/CD integration. By pre-processing PR entries during development and aggregating them during releases, we achieve both accuracy and automation without sacrificing quality or developer productivity.
|
||||
|
||||
The phased approach ensures smooth adoption while the extensive error handling and validation provide confidence in production deployment. The system's design leverages existing infrastructure and patterns, making it a natural evolution of the current changelog generation capabilities.
|
||||
195
docs/Automated-Changelog-Usage.md
Normal file
195
docs/Automated-Changelog-Usage.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# Automated Changelog System - Developer Guide
|
||||
|
||||
This guide explains how to use the new automated changelog system for the Fabric project.
|
||||
|
||||
## Overview
|
||||
|
||||
The automated changelog system allows developers to pre-process their PR changelog entries during development, which are then automatically aggregated during the release process. This eliminates manual CHANGELOG.md editing and reduces merge conflicts.
|
||||
|
||||
## Developer Workflow
|
||||
|
||||
### Step 1: Create Your Feature Branch and PR
|
||||
|
||||
Work on your feature as usual and create a pull request.
|
||||
|
||||
### Step 2: Generate Changelog Entry
|
||||
|
||||
Once your PR is ready for review, generate a changelog entry:
|
||||
|
||||
```bash
|
||||
cd cmd/generate_changelog
|
||||
go build -o generate_changelog .
|
||||
./generate_changelog --incoming-pr YOUR_PR_NUMBER
|
||||
```
|
||||
|
||||
For example, if your PR number is 1672:
|
||||
|
||||
```bash
|
||||
./generate_changelog --incoming-pr 1672
|
||||
```
|
||||
|
||||
### Step 3: Validation
|
||||
|
||||
The tool will validate:
|
||||
|
||||
- ✅ PR exists and is open
|
||||
- ✅ PR is mergeable (no conflicts)
|
||||
- ✅ Your working directory is clean
|
||||
|
||||
If any validation fails, fix the issues and try again.
|
||||
|
||||
### Step 4: Review Generated Entry
|
||||
|
||||
The tool will:
|
||||
|
||||
1. Create `./cmd/generate_changelog/incoming/1672.txt`
|
||||
2. Generate an AI-enhanced summary (if `--ai-summarize` is enabled)
|
||||
3. Auto-commit the file to your branch (use `--push` to also push to remote)
|
||||
|
||||
Review the generated file and edit if needed:
|
||||
|
||||
```bash
|
||||
cat ./cmd/generate_changelog/incoming/1672.txt
|
||||
```
|
||||
|
||||
### Step 5: Include in PR
|
||||
|
||||
The incoming changelog entry is now part of your PR and will be reviewed along with your code changes.
|
||||
|
||||
## Example Generated Entry
|
||||
|
||||
```markdown
|
||||
### PR [#1672](https://github.com/danielmiessler/fabric/pull/1672) by [ksylvan](https://github.com/ksylvan): Changelog Generator Enhancement
|
||||
|
||||
- Added automated CI/CD integration for changelog generation
|
||||
- Implemented pre-processing of PR entries during development
|
||||
- Enhanced caching system for better performance
|
||||
- Added validation for mergeable PR states
|
||||
```
|
||||
|
||||
## Command Options
|
||||
|
||||
### `--incoming-pr`
|
||||
|
||||
Pre-process a specific PR for changelog generation.
|
||||
|
||||
**Usage**: `./generate_changelog --incoming-pr PR_NUMBER`
|
||||
|
||||
**Requirements**:
|
||||
|
||||
- PR must be open
|
||||
- PR must be mergeable (no conflicts)
|
||||
- Working directory must be clean (no uncommitted changes)
|
||||
- GitHub token must be available (`GITHUB_TOKEN` env var or `--token` flag)
|
||||
|
||||
**Mutual Exclusivity**: Cannot be used with `--process-prs` flag
|
||||
|
||||
### `--incoming-dir`
|
||||
|
||||
Specify custom directory for incoming PR files (default: `./cmd/generate_changelog/incoming`).
|
||||
|
||||
**Usage**: `./generate_changelog --incoming-pr 1672 --incoming-dir ./custom/path`
|
||||
|
||||
### `--process-prs`
|
||||
|
||||
Process all incoming PR files for release aggregation. Used by CI/CD during release creation.
|
||||
|
||||
**Usage**: `./generate_changelog --process-prs {new_version_string}`
|
||||
|
||||
**Mutual Exclusivity**: Cannot be used with `--incoming-pr` flag
|
||||
|
||||
### `--ai-summarize`
|
||||
|
||||
Enable AI-enhanced summaries using Fabric integration.
|
||||
|
||||
**Usage**: `./generate_changelog --incoming-pr 1672 --ai-summarize`
|
||||
|
||||
### `--push`
|
||||
|
||||
Enable automatic git push after creating an incoming entry. By default, the commit is created locally but not pushed to the remote repository.
|
||||
|
||||
**Usage**: `./generate_changelog --incoming-pr 1672 --push`
|
||||
|
||||
**Note**: When using `--push`, ensure you have proper authentication configured (SSH keys or GITHUB_TOKEN environment variable).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "PR is not open"
|
||||
|
||||
Your PR has been closed or merged. Only open PRs can be processed.
|
||||
|
||||
### "PR is not mergeable"
|
||||
|
||||
Your PR has merge conflicts or other issues preventing it from being merged. Resolve conflicts and ensure the PR is in a mergeable state.
|
||||
|
||||
### "Working directory is not clean"
|
||||
|
||||
You have uncommitted changes. Commit or stash them before running the tool.
|
||||
|
||||
### "Failed to fetch PR"
|
||||
|
||||
Check your GitHub token and network connection. Ensure the PR number exists.
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
The system automatically processes all incoming PR files during the release workflow. No manual intervention is required.
|
||||
|
||||
When a release is created:
|
||||
|
||||
1. All `incoming/*.txt` files are aggregated using `--process-prs`
|
||||
2. Version is detected from `version.nix` or latest git tag
|
||||
3. A new version entry is created in CHANGELOG.md
|
||||
4. Incoming files are cleaned up (removed)
|
||||
5. Changes are staged for the release commit (CHANGELOG.md and cache file)
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Run early**: Generate your changelog entry as soon as your PR is ready for review
|
||||
2. **Review content**: Always review the generated entry and edit if necessary
|
||||
3. **Keep it updated**: If you make significant changes to your PR, regenerate the entry
|
||||
4. **Use AI summaries**: Enable `--ai-summarize` for more professional, consistent formatting
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Custom GitHub Token
|
||||
|
||||
```bash
|
||||
./generate_changelog --incoming-pr 1672 --token YOUR_GITHUB_TOKEN
|
||||
```
|
||||
|
||||
### Custom Repository Path
|
||||
|
||||
```bash
|
||||
./generate_changelog --incoming-pr 1672 --repo /path/to/repo
|
||||
```
|
||||
|
||||
### Disable Caching
|
||||
|
||||
```bash
|
||||
./generate_changelog --incoming-pr 1672 --no-cache
|
||||
```
|
||||
|
||||
### Enable Auto-Push
|
||||
|
||||
```bash
|
||||
./generate_changelog --incoming-pr 1672 --push
|
||||
```
|
||||
|
||||
This creates the commit locally and pushes it to the remote repository. By default, commits are only created locally, allowing you to review changes before pushing manually.
|
||||
|
||||
**Authentication**: The tool automatically detects GitHub repositories and uses the GITHUB_TOKEN environment variable for authentication when pushing. For SSH repositories, ensure your SSH keys are properly configured.
|
||||
|
||||
## Integration with Existing Workflow
|
||||
|
||||
This system is fully backward compatible. The existing changelog generation continues to work unchanged. The new features are opt-in and only activated when using the new flags.
|
||||
|
||||
## Support
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
1. Check this documentation
|
||||
2. Verify your GitHub token has appropriate permissions
|
||||
3. Ensure your PR meets the validation requirements
|
||||
4. Check the tool's help: `./generate_changelog --help`
|
||||
|
||||
For bugs or feature requests, please create an issue in the repository.
|
||||
26
docs/CODE_OF_CONDUCT.md
Normal file
26
docs/CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Code of Conduct
|
||||
|
||||
## Our Expectation
|
||||
|
||||
We expect all contributors and community members to act with basic human decency and common sense.
|
||||
|
||||
This project exists to help people augment their capabilities with AI, and we welcome contributions from anyone who shares this mission. We assume good faith and trust that everyone involved is here to build something valuable together.
|
||||
|
||||
## Guidelines
|
||||
|
||||
- **Be respectful**: Treat others as you'd want to be treated in a professional setting
|
||||
- **Be constructive**: Focus on the work and help make the project better
|
||||
- **Be collaborative**: We're all working toward the same goal - making Fabric more useful
|
||||
- **Use good judgment**: If you're not sure whether something is appropriate, it probably isn't
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
If someone is being genuinely disruptive or harmful, please email the maintainers directly. We'll address legitimate concerns promptly and fairly.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Maintainers reserve the right to remove content and restrict access for anyone who consistently acts in bad faith or disrupts the community.
|
||||
|
||||
---
|
||||
|
||||
*This project assumes contributors are adults who can work together professionally. If you can't do that, this isn't the right place for you.*
|
||||
155
docs/CONTRIBUTING.md
Normal file
155
docs/CONTRIBUTING.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# Contributing to Fabric
|
||||
|
||||
Thanks for contributing to Fabric! Here's what you need to know to get started quickly.
|
||||
|
||||
## Quick Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Go 1.24+ installed
|
||||
- Git configured with your details
|
||||
|
||||
### Getting Started
|
||||
|
||||
```bash
|
||||
# Clone and setup
|
||||
git clone https://github.com/danielmiessler/fabric.git
|
||||
cd fabric
|
||||
go build -o fabric ./cmd/fabric
|
||||
./fabric --setup
|
||||
|
||||
# Run tests
|
||||
go test ./...
|
||||
```
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
### Code Style
|
||||
|
||||
- Follow standard Go conventions (`gofmt`, `golint`)
|
||||
- Use meaningful variable and function names
|
||||
- Write tests for new functionality
|
||||
- Keep functions focused and small
|
||||
|
||||
### Commit Messages
|
||||
|
||||
Use descriptive commit messages:
|
||||
|
||||
```text
|
||||
feat: add new pattern for code analysis
|
||||
fix: resolve OAuth token refresh issue
|
||||
docs: update installation instructions
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
- `cmd/` - Executable commands
|
||||
- `internal/` - Private application code
|
||||
- `data/patterns/` - AI patterns
|
||||
- `docs/` - Documentation
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
### Changelog Generation (REQUIRED)
|
||||
|
||||
Before submitting your PR, generate a changelog entry:
|
||||
|
||||
```bash
|
||||
cd cmd/generate_changelog
|
||||
go build -o generate_changelog .
|
||||
./generate_changelog --incoming-pr YOUR_PR_NUMBER
|
||||
```
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- PR must be open and mergeable
|
||||
- Working directory must be clean
|
||||
- GitHub token available (GITHUB_TOKEN env var)
|
||||
|
||||
**Optional flags:**
|
||||
|
||||
- `--ai-summarize` - Enhanced AI-generated summaries
|
||||
- `--push` - Auto-push the changelog commit
|
||||
|
||||
### PR Guidelines
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Write/update tests
|
||||
5. Generate changelog entry (see above)
|
||||
6. Submit PR with clear description
|
||||
|
||||
### Review Process
|
||||
|
||||
- PRs require maintainer review
|
||||
- Address feedback promptly
|
||||
- Keep PRs focused on single features/fixes
|
||||
- Update changelog if you make significant changes
|
||||
|
||||
## Testing
|
||||
|
||||
### Run Tests
|
||||
|
||||
```bash
|
||||
# All tests
|
||||
go test ./...
|
||||
|
||||
# Specific package
|
||||
go test ./internal/cli
|
||||
|
||||
# With coverage
|
||||
go test -cover ./...
|
||||
```
|
||||
|
||||
### Test Requirements
|
||||
|
||||
- Unit tests for core functionality
|
||||
- Integration tests for external dependencies
|
||||
- Examples in documentation
|
||||
|
||||
## Patterns
|
||||
|
||||
### Creating Patterns
|
||||
|
||||
Patterns go in `data/patterns/[pattern-name]/system.md`:
|
||||
|
||||
```markdown
|
||||
# IDENTITY and PURPOSE
|
||||
You are an expert at...
|
||||
|
||||
# STEPS
|
||||
- Step 1
|
||||
- Step 2
|
||||
|
||||
# OUTPUT
|
||||
- Output format requirements
|
||||
|
||||
# EXAMPLE
|
||||
Example output here
|
||||
```
|
||||
|
||||
### Pattern Guidelines
|
||||
|
||||
- Use clear, actionable language
|
||||
- Provide specific output formats
|
||||
- Include examples when helpful
|
||||
- Test with multiple AI providers
|
||||
|
||||
## Documentation
|
||||
|
||||
- Update README.md for new features
|
||||
- Add docs to `docs/` for complex features
|
||||
- Include usage examples
|
||||
- Keep documentation current
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Check existing issues first
|
||||
- Ask questions in discussions
|
||||
- Tag maintainers for urgent issues
|
||||
- Be patient - maintainers are volunteers
|
||||
|
||||
## License
|
||||
|
||||
By contributing, you agree your contributions will be licensed under the MIT License.
|
||||
183
docs/Desktop-Notifications.md
Normal file
183
docs/Desktop-Notifications.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# Desktop Notifications
|
||||
|
||||
Fabric supports desktop notifications to alert you when commands complete, which is especially useful for long-running tasks or when you're multitasking.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Enable notifications with the `--notification` flag:
|
||||
|
||||
```bash
|
||||
fabric --pattern summarize --notification < article.txt
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Command Line Options
|
||||
|
||||
- `--notification`: Enable desktop notifications when command completes
|
||||
- `--notification-command`: Use a custom notification command instead of built-in notifications
|
||||
|
||||
### YAML Configuration
|
||||
|
||||
Add notification settings to your `~/.config/fabric/config.yaml`:
|
||||
|
||||
```yaml
|
||||
# Enable notifications by default
|
||||
notification: true
|
||||
|
||||
# Optional: Custom notification command
|
||||
notificationCommand: 'notify-send --urgency=normal "$1" "$2"'
|
||||
```
|
||||
|
||||
## Platform Support
|
||||
|
||||
### macOS
|
||||
|
||||
- **Default**: Uses `osascript` (built into macOS)
|
||||
- **Enhanced**: Install `terminal-notifier` for better notifications:
|
||||
|
||||
```bash
|
||||
brew install terminal-notifier
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
- **Requirement**: Install `notify-send`:
|
||||
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt install libnotify-bin
|
||||
|
||||
# Fedora
|
||||
sudo dnf install libnotify
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
- **Default**: Uses PowerShell message boxes (built-in)
|
||||
|
||||
## Custom Notification Commands
|
||||
|
||||
The `--notification-command` flag allows you to use custom notification scripts or commands. The command receives the title as `$1` and message as `$2` as shell positional arguments.
|
||||
|
||||
**Security Note**: The title and message content are properly escaped to prevent command injection attacks from AI-generated output containing shell metacharacters.
|
||||
|
||||
### Examples
|
||||
|
||||
**macOS with custom sound:**
|
||||
|
||||
```bash
|
||||
fabric --pattern analyze_claims --notification-command 'osascript -e "display notification \"$2\" with title \"$1\" sound name \"Ping\""' < document.txt
|
||||
```
|
||||
|
||||
**Linux with urgency levels:**
|
||||
|
||||
```bash
|
||||
fabric --pattern extract_wisdom --notification-command 'notify-send --urgency=critical "$1" "$2"' < video-transcript.txt
|
||||
```
|
||||
|
||||
**Custom script:**
|
||||
|
||||
```bash
|
||||
fabric --pattern summarize --notification-command '/path/to/my-notification-script.sh "$1" "$2"' < report.pdf
|
||||
```
|
||||
|
||||
**Testing your custom command:**
|
||||
|
||||
```bash
|
||||
# Test that $1 and $2 are passed correctly
|
||||
fabric --pattern raw_query --notification-command 'echo "Title: $1, Message: $2"' "test input"
|
||||
```
|
||||
|
||||
## Notification Content
|
||||
|
||||
Notifications include:
|
||||
|
||||
- **Title**: "Fabric Command Complete" or "Fabric: [pattern] Complete"
|
||||
- **Message**: Brief summary of the output (first 100 characters)
|
||||
|
||||
For long outputs, the message is truncated with "..." to fit notification display limits.
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Long-Running Tasks
|
||||
|
||||
```bash
|
||||
# Process large document with notifications
|
||||
fabric --pattern analyze_paper --notification < research-paper.pdf
|
||||
|
||||
# Extract wisdom from long video with alerts
|
||||
fabric -y "https://youtube.com/watch?v=..." --pattern extract_wisdom --notification
|
||||
```
|
||||
|
||||
### Background Processing
|
||||
|
||||
```bash
|
||||
# Process multiple files and get notified when each completes
|
||||
for file in *.txt; do
|
||||
fabric --pattern summarize --notification < "$file" &
|
||||
done
|
||||
```
|
||||
|
||||
### Integration with Other Tools
|
||||
|
||||
```bash
|
||||
# Combine with other commands
|
||||
curl -s "https://api.example.com/data" | \
|
||||
fabric --pattern analyze_data --notification --output results.md
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No Notifications Appearing
|
||||
|
||||
1. **Check system notifications are enabled** for Terminal/your shell
|
||||
2. **Verify notification tools are installed**:
|
||||
- macOS: `which osascript` (should exist)
|
||||
- Linux: `which notify-send`
|
||||
- Windows: `where.exe powershell`
|
||||
|
||||
3. **Test with simple command**:
|
||||
|
||||
```bash
|
||||
echo "test" | fabric --pattern raw_query --notification --dry-run
|
||||
```
|
||||
|
||||
### Notification Permission Issues
|
||||
|
||||
On some systems, you may need to grant notification permissions to your terminal application:
|
||||
|
||||
- **macOS**: System Preferences → Security & Privacy → Privacy → Notifications → Enable for Terminal
|
||||
- **Linux**: Depends on desktop environment; usually automatic
|
||||
- **Windows**: Usually works by default
|
||||
|
||||
### Custom Commands Not Working
|
||||
|
||||
- Ensure your custom notification command is executable
|
||||
- Test the command manually with sample arguments
|
||||
- Check that all required dependencies are installed
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Environment-Specific Settings
|
||||
|
||||
Create different configuration files for different environments:
|
||||
|
||||
```bash
|
||||
# Work computer (quieter notifications)
|
||||
fabric --config ~/.config/fabric/work-config.yaml --notification
|
||||
|
||||
# Personal computer (with sound)
|
||||
fabric --config ~/.config/fabric/personal-config.yaml --notification
|
||||
```
|
||||
|
||||
### Integration with Task Management
|
||||
|
||||
```bash
|
||||
# Custom script that also logs to task management system
|
||||
notificationCommand: '/usr/local/bin/fabric-notify-and-log.sh "$1" "$2"'
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
See `docs/notification-config.yaml` for a complete configuration example with various notification command options.
|
||||
155
docs/Gemini-TTS.md
Normal file
155
docs/Gemini-TTS.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# Gemini Text-to-Speech (TTS) Guide
|
||||
|
||||
Fabric supports Google Gemini's text-to-speech (TTS) capabilities, allowing you to convert text into high-quality audio using various AI-generated voices.
|
||||
|
||||
## Overview
|
||||
|
||||
The Gemini TTS feature in Fabric allows you to:
|
||||
|
||||
- Convert text input into audio using Google's Gemini TTS models
|
||||
- Choose from 30+ different AI voices with varying characteristics
|
||||
- Generate high-quality WAV audio files
|
||||
- Integrate TTS generation into your existing Fabric workflows
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic TTS Generation
|
||||
|
||||
To generate audio from text using TTS:
|
||||
|
||||
```bash
|
||||
# Basic TTS with default voice (Kore)
|
||||
echo "Hello, this is a test of Gemini TTS" | fabric -m gemini-2.5-flash-preview-tts -o output.wav
|
||||
|
||||
# Using a specific voice
|
||||
echo "Hello, this is a test with the Charon voice" | fabric -m gemini-2.5-flash-preview-tts --voice Charon -o output.wav
|
||||
|
||||
# Using TTS with a pattern
|
||||
fabric -p summarize --voice Puck -m gemini-2.5-flash-preview-tts -o summary.wav < document.txt
|
||||
```
|
||||
|
||||
### Voice Selection
|
||||
|
||||
Use the `--voice` flag to specify which voice to use for TTS generation:
|
||||
|
||||
```bash
|
||||
fabric -m gemini-2.5-flash-preview-tts --voice Zephyr -o output.wav "Your text here"
|
||||
```
|
||||
|
||||
If no voice is specified, the default voice "Kore" will be used.
|
||||
|
||||
## Available Voices
|
||||
|
||||
Gemini TTS supports 30+ different voices, each with unique characteristics:
|
||||
|
||||
### Popular Voices
|
||||
|
||||
- **Kore** - Firm and confident (default)
|
||||
- **Charon** - Informative and clear
|
||||
- **Puck** - Upbeat and energetic
|
||||
- **Zephyr** - Bright and cheerful
|
||||
- **Leda** - Youthful and energetic
|
||||
- **Aoede** - Breezy and natural
|
||||
|
||||
### Complete Voice List
|
||||
|
||||
- Kore, Charon, Puck, Fenrir, Aoede, Leda, Orus, Zephyr
|
||||
- Autonoe, Callirhoe, Despina, Erinome, Gacrux, Laomedeia
|
||||
- Pulcherrima, Sulafat, Vindemiatrix, Achernar, Achird
|
||||
- Algenib, Algieba, Alnilam, Enceladus, Iapetus, Rasalgethi
|
||||
- Sadachbia, Zubenelgenubi, Vega, Capella, Lyra
|
||||
|
||||
### Listing Available Voices
|
||||
|
||||
To see all available voices with descriptions:
|
||||
|
||||
```bash
|
||||
# List all voices with characteristics
|
||||
fabric --list-gemini-voices
|
||||
|
||||
# List voice names only (for shell completion)
|
||||
fabric --list-gemini-voices --shell-complete-list
|
||||
```
|
||||
|
||||
## Rate Limits
|
||||
|
||||
Google Gemini TTS has usage quotas that vary by plan:
|
||||
|
||||
### Free Tier
|
||||
|
||||
- **15 requests per day** per project per TTS model
|
||||
- Quota resets daily
|
||||
- Applies to all TTS models (e.g., `gemini-2.5-flash-preview-tts`)
|
||||
|
||||
### Rate Limit Errors
|
||||
|
||||
If you exceed your quota, you'll see an error like:
|
||||
|
||||
```text
|
||||
Error 429: You exceeded your current quota, please check your plan and billing details
|
||||
```
|
||||
|
||||
**Solutions:**
|
||||
|
||||
- Wait for daily quota reset (typically at midnight UTC)
|
||||
- Upgrade to a paid plan for higher limits
|
||||
- Use TTS generation strategically for important content
|
||||
|
||||
For current rate limits and pricing, visit: <https://ai.google.dev/gemini-api/docs/rate-limits>
|
||||
|
||||
## Configuration
|
||||
|
||||
### Command Line Options
|
||||
|
||||
- `--voice <voice_name>` - Specify the TTS voice to use
|
||||
- `-o <filename.wav>` - Output audio file (required for TTS models)
|
||||
- `-m <tts_model>` - Specify a TTS-capable model (e.g., `gemini-2.5-flash-preview-tts`)
|
||||
|
||||
### YAML Configuration
|
||||
|
||||
You can also set a default voice in your Fabric configuration file (`~/.config/fabric/config.yaml`):
|
||||
|
||||
```yaml
|
||||
voice: "Charon" # Set your preferred default voice
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Valid Google Gemini API key configured in Fabric
|
||||
- TTS-capable Gemini model (models containing "tts" in the name)
|
||||
- Audio output must be specified with `-o filename.wav`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Error: "TTS model requires audio output"
|
||||
|
||||
- Solution: Always specify an output file with `-o filename.wav` when using TTS models
|
||||
|
||||
#### Error: "Invalid voice 'X'"
|
||||
|
||||
- Solution: Check that the voice name is spelled correctly and matches one of the supported voices listed above
|
||||
|
||||
#### Error: "TTS generation failed"
|
||||
|
||||
- Solution: Verify your Gemini API key is valid and you have sufficient quota
|
||||
|
||||
### Getting Help
|
||||
|
||||
For additional help with TTS features:
|
||||
|
||||
```bash
|
||||
fabric --help
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
|
||||
- **Audio Format**: WAV files with 24kHz sample rate, 16-bit depth, mono channel
|
||||
- **Language Support**: Automatic language detection for 24+ languages
|
||||
- **Model Requirements**: Models must contain "tts", "preview-tts", or "text-to-speech" in the name
|
||||
- **Voice Selection**: Uses Google's PrebuiltVoiceConfig system for consistent voice quality
|
||||
|
||||
---
|
||||
|
||||
For more information about Fabric, visit the [main documentation](../README.md).
|
||||
88
docs/README.md
Normal file
88
docs/README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Fabric Documentation
|
||||
|
||||
Welcome to the Fabric documentation! This directory contains detailed guides and technical documentation for various features and components of Fabric.
|
||||
|
||||
## 📚 Available Documentation
|
||||
|
||||
### Core Features
|
||||
|
||||
**[Automated-Changelog-Usage.md](./Automated-Changelog-Usage.md)**
|
||||
Complete guide for developers on using the automated changelog system. Covers the workflow for generating PR changelog entries during development, including setup, validation, and CI/CD integration.
|
||||
|
||||
**[YouTube-Processing.md](./YouTube-Processing.md)**
|
||||
Comprehensive guide for processing YouTube videos and playlists with Fabric. Covers transcript extraction, comment processing, metadata retrieval, and advanced yt-dlp configurations.
|
||||
|
||||
**[Using-Speech-To-Text.md](./Using-Speech-To-Text.md)**
|
||||
Documentation for Fabric's speech-to-text capabilities using OpenAI's Whisper models. Learn how to transcribe audio and video files and process them through Fabric patterns.
|
||||
|
||||
### User Interface & Experience
|
||||
|
||||
**[Desktop-Notifications.md](./Desktop-Notifications.md)**
|
||||
Guide to setting up desktop notifications for Fabric commands. Useful for long-running tasks and multitasking scenarios with cross-platform notification support.
|
||||
|
||||
**[Shell-Completions.md](./Shell-Completions.md)**
|
||||
Instructions for setting up intelligent tab completion for Fabric in Zsh, Bash, and Fish shells. Includes automated installation and manual setup options.
|
||||
|
||||
**[Gemini-TTS.md](./Gemini-TTS.md)**
|
||||
Complete guide for using Google Gemini's text-to-speech features with Fabric. Covers voice selection, audio generation, and integration with Fabric patterns.
|
||||
|
||||
### Development & Architecture
|
||||
|
||||
**[Automated-ChangeLog.md](./Automated-ChangeLog.md)**
|
||||
Technical documentation outlining the automated CHANGELOG system architecture for CI/CD integration. Details the infrastructure and workflow for maintainers.
|
||||
|
||||
**[Project-Restructured.md](./Project-Restructured.md)**
|
||||
Project restructuring plan and architectural decisions. Documents the transition to standard Go conventions and project organization improvements.
|
||||
|
||||
**[NOTES.md](./NOTES.md)**
|
||||
Development notes on refactoring efforts, model management improvements, and architectural changes. Includes technical details on vendor and model abstraction.
|
||||
|
||||
### Audio Resources
|
||||
|
||||
**[voices/README.md](./voices/README.md)**
|
||||
Index of Gemini TTS voice samples demonstrating different AI voice characteristics available in Fabric.
|
||||
|
||||
## 🗂️ Additional Resources
|
||||
|
||||
### Configuration Files
|
||||
|
||||
- `./notification-config.yaml` - Example notification configuration
|
||||
|
||||
### Images
|
||||
|
||||
- `images/` - Screenshots and visual documentation assets
|
||||
- `fabric-logo-gif.gif` - Animated Fabric logo
|
||||
- `fabric-summarize.png` - Screenshot of summarization feature
|
||||
- `svelte-preview.png` - Web interface preview
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
New to Fabric? Start with these essential docs:
|
||||
|
||||
1. **[../README.md](../README.md)** - Main project README with installation and basic usage
|
||||
2. **[Shell-Completions.md](./Shell-Completions.md)** - Set up tab completion for better CLI experience
|
||||
3. **[YouTube-Processing.md](./YouTube-Processing.md)** - Learn one of Fabric's most popular features
|
||||
4. **[Desktop-Notifications.md](./Desktop-Notifications.md)** - Get notified when long tasks complete
|
||||
|
||||
## 🔧 For Contributors
|
||||
|
||||
Contributing to Fabric? These docs are essential:
|
||||
|
||||
1. **[./CONTRIBUTING.md](./CONTRIBUTING.md)** - Contribution guidelines and setup
|
||||
2. **[Automated-Changelog-Usage.md](./Automated-Changelog-Usage.md)** - Required workflow for PR submissions
|
||||
3. **[Project-Restructured.md](./Project-Restructured.md)** - Understanding project architecture
|
||||
4. **[NOTES.md](./NOTES.md)** - Current development priorities and patterns
|
||||
|
||||
## 📝 Documentation Standards
|
||||
|
||||
When adding new documentation:
|
||||
|
||||
- Use clear, descriptive filenames
|
||||
- Include practical examples and use cases
|
||||
- Update this README index with your new docs
|
||||
- Follow the established markdown formatting conventions
|
||||
- Test all code examples before publication
|
||||
|
||||
---
|
||||
|
||||
*For general help and support, see [./SUPPORT.md](./SUPPORT.md)*
|
||||
158
docs/SECURITY.md
Normal file
158
docs/SECURITY.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We aim to provide security updates for the latest version of Fabric.
|
||||
|
||||
We recommend always using the latest version of Fabric for security fixes and improvements.
|
||||
|
||||
## Reporting Security Vulnerabilities
|
||||
|
||||
**Please DO NOT report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
### Preferred Reporting Method
|
||||
|
||||
Send security reports directly to: **<kayvan@sylvan.com>** and CC to the project maintainer at **<daniel@danielmiessler.com>**
|
||||
|
||||
### What to Include
|
||||
|
||||
Please provide the following information:
|
||||
|
||||
1. **Vulnerability Type**: What kind of security issue (e.g., injection, authentication bypass, etc.)
|
||||
2. **Affected Components**: Which parts of Fabric are affected
|
||||
3. **Impact Assessment**: What could an attacker accomplish
|
||||
4. **Reproduction Steps**: Clear steps to reproduce the vulnerability
|
||||
5. **Proposed Fix**: If you have suggestions for remediation
|
||||
6. **Disclosure Timeline**: Your preferred timeline for public disclosure
|
||||
|
||||
### Example Report Format
|
||||
|
||||
```text
|
||||
Subject: [SECURITY] Brief description of vulnerability
|
||||
|
||||
Vulnerability Type: SQL Injection
|
||||
Affected Component: Pattern database queries
|
||||
Impact: Potential data exposure
|
||||
Severity: High
|
||||
|
||||
Reproduction Steps:
|
||||
1. Navigate to...
|
||||
2. Submit payload: ...
|
||||
3. Observe...
|
||||
|
||||
Evidence:
|
||||
[Screenshots, logs, or proof of concept]
|
||||
|
||||
Suggested Fix:
|
||||
Use parameterized queries instead of string concatenation...
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### API Keys and Secrets
|
||||
|
||||
- Never commit API keys to the repository
|
||||
- Store secrets in environment variables or secure configuration
|
||||
- Use the built-in setup process for key management
|
||||
- Regularly rotate API keys
|
||||
|
||||
### Input Validation
|
||||
|
||||
- All user inputs are validated before processing
|
||||
- Special attention to pattern definitions and user content
|
||||
- URL validation for web scraping features
|
||||
|
||||
### AI Provider Integration
|
||||
|
||||
- Secure communication with AI providers (HTTPS/TLS)
|
||||
- Token handling follows provider best practices
|
||||
- No sensitive data logged or cached unencrypted
|
||||
|
||||
### Network Security
|
||||
|
||||
- Web server endpoints properly authenticated when required
|
||||
- CORS policies appropriately configured
|
||||
- Rate limiting implemented where necessary
|
||||
|
||||
## Vulnerability Response Process
|
||||
|
||||
1. **Report Received**: We'll acknowledge receipt within 24 hours
|
||||
2. **Initial Assessment**: We'll evaluate severity and impact within 72 hours
|
||||
3. **Investigation**: We'll investigate and develop fixes
|
||||
4. **Fix Development**: We'll create and test patches
|
||||
5. **Coordinated Disclosure**: We'll work with reporter on disclosure timeline
|
||||
6. **Release**: We'll release patched version with security advisory
|
||||
|
||||
### Timeline Expectations
|
||||
|
||||
- **Critical**: 1-7 days
|
||||
- **High**: 7-30 days
|
||||
- **Medium**: 30-90 days
|
||||
- **Low**: Next scheduled release
|
||||
|
||||
## Bug Bounty
|
||||
|
||||
We don't currently offer a formal bug bounty program, but we deeply appreciate security research and will:
|
||||
|
||||
- Acknowledge contributors in release notes
|
||||
- Provide credit in security advisories
|
||||
- Consider swag or small rewards for significant findings
|
||||
|
||||
## Security Best Practices for Users
|
||||
|
||||
### Installation
|
||||
|
||||
- Download Fabric only from official sources
|
||||
- Verify checksums when available
|
||||
- Keep installations up to date
|
||||
|
||||
### Configuration
|
||||
|
||||
- Use strong, unique API keys
|
||||
- Don't share configuration files containing secrets
|
||||
- Set appropriate file permissions on config directories
|
||||
|
||||
### Usage
|
||||
|
||||
- Be cautious with patterns that process sensitive data
|
||||
- Review AI provider terms for data handling
|
||||
- Consider using local models for sensitive content
|
||||
|
||||
## Known Security Limitations
|
||||
|
||||
### AI Provider Dependencies
|
||||
|
||||
Fabric relies on external AI providers. Security depends partly on:
|
||||
|
||||
- Provider security practices
|
||||
- Data transmission security
|
||||
- Provider data handling policies
|
||||
|
||||
### Pattern Execution
|
||||
|
||||
Custom patterns could potentially:
|
||||
|
||||
- Process sensitive inputs inappropriately
|
||||
- Generate outputs containing sensitive information
|
||||
- Be used for adversarial prompt injection
|
||||
|
||||
**Recommendation**: Review patterns carefully, especially those from untrusted sources.
|
||||
|
||||
## Security Updates
|
||||
|
||||
Security updates are distributed through:
|
||||
|
||||
- GitHub Releases with security tags
|
||||
- Security advisories on GitHub
|
||||
- Project documentation updates
|
||||
|
||||
Subscribe to the repository to receive notifications about security updates.
|
||||
|
||||
## Contact
|
||||
|
||||
For non-security issues, please use GitHub issues.
|
||||
For security concerns, email: **<kayvan@sylvan.com>** and CC to **<daniel@danielmiessler.com>**
|
||||
|
||||
---
|
||||
|
||||
*We take security seriously and appreciate the security research community's help in keeping Fabric secure.*
|
||||
148
docs/SUPPORT.md
Normal file
148
docs/SUPPORT.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# Support
|
||||
|
||||
## Getting Help with Fabric
|
||||
|
||||
Need help with Fabric? Here are the best ways to get assistance:
|
||||
|
||||
## 📖 Documentation First
|
||||
|
||||
Before reaching out, check these resources:
|
||||
|
||||
- **[README.md](../README.md)** - Installation, usage, and examples
|
||||
- **[docs/](./README.md)** - Detailed documentation
|
||||
- **[Patterns](../data/patterns/)** - Browse available AI patterns
|
||||
|
||||
## 🐛 Bug Reports
|
||||
|
||||
Found a bug? Please create an issue:
|
||||
|
||||
**[Report a Bug](https://github.com/danielmiessler/fabric/issues/new?template=bug.yml)**
|
||||
|
||||
Include:
|
||||
|
||||
- Fabric version (`fabric --version`)
|
||||
- Operating system
|
||||
- Steps to reproduce
|
||||
- Expected vs actual behavior
|
||||
- Error messages/logs
|
||||
|
||||
## 💡 Feature Requests
|
||||
|
||||
Have an idea for Fabric? We'd love to hear it:
|
||||
|
||||
**[Request a Feature](https://github.com/danielmiessler/fabric/issues/new)**
|
||||
|
||||
Describe:
|
||||
|
||||
- What you want to achieve
|
||||
- Why it would be useful
|
||||
- How you envision it working
|
||||
- Any alternatives you've considered
|
||||
|
||||
## 🤔 Questions & Discussions
|
||||
|
||||
For general questions, usage help, or community discussion:
|
||||
|
||||
**[GitHub Discussions](https://github.com/danielmiessler/fabric/discussions)**
|
||||
|
||||
Great for:
|
||||
|
||||
- "How do I...?" questions
|
||||
- Sharing patterns you've created
|
||||
- Getting community advice
|
||||
- Feature brainstorming
|
||||
|
||||
## 🏷️ Issue Labels
|
||||
|
||||
When creating issues, maintainers will add appropriate labels:
|
||||
|
||||
- `bug` - Something isn't working
|
||||
- `enhancement` - New feature request
|
||||
- `documentation` - Documentation improvements
|
||||
- `help wanted` - Community contributions welcome
|
||||
- `good first issue` - Great for new contributors
|
||||
- `question` - General questions
|
||||
- `pattern` - Related to AI patterns
|
||||
|
||||
## 📋 Issue Templates
|
||||
|
||||
We provide templates to help you create detailed reports:
|
||||
|
||||
- **Bug Report** - Structured bug reporting
|
||||
- **Feature Request** - Detailed feature proposals
|
||||
- **Pattern Submission** - New pattern contributions
|
||||
|
||||
## 🔒 Security Issues
|
||||
|
||||
**DO NOT create public issues for security vulnerabilities.**
|
||||
|
||||
See our [Security Policy](./SECURITY.md) for proper reporting procedures.
|
||||
|
||||
## ⚡ Response Times
|
||||
|
||||
We're a community-driven project with volunteer maintainers:
|
||||
|
||||
- **Bugs**: We aim to acknowledge within 48 hours
|
||||
- **Features**: Response time varies based on complexity
|
||||
- **Questions**: Community often responds quickly
|
||||
- **Security**: See security policy for timelines
|
||||
|
||||
## 🛠️ Self-Help Tips
|
||||
|
||||
Before creating an issue, try:
|
||||
|
||||
1. **Update Fabric**: `go install github.com/danielmiessler/fabric/cmd/fabric@latest`
|
||||
2. **Check existing issues**: Someone might have the same problem
|
||||
3. **Run setup**: `fabric --setup` can fix configuration issues
|
||||
4. **Test minimal example**: Isolate the problem
|
||||
|
||||
## 🤝 Community Guidelines
|
||||
|
||||
When asking for help:
|
||||
|
||||
- Be specific and provide context
|
||||
- Include relevant details and error messages
|
||||
- Be patient - maintainers are volunteers
|
||||
- Help others when you can
|
||||
- Say thanks when someone helps you
|
||||
|
||||
## 📞 Emergency Contact
|
||||
|
||||
For urgent security issues only:
|
||||
|
||||
- Email: <security@fabric.ai> (if available)
|
||||
- Maintainer: <daniel@danielmiessler.com>
|
||||
|
||||
## 🎯 What We Can Help With
|
||||
|
||||
✅ **We can help with:**
|
||||
|
||||
- Installation and setup issues
|
||||
- Usage questions and examples
|
||||
- Bug reports and fixes
|
||||
- Feature discussions
|
||||
- Pattern creation guidance
|
||||
- Integration questions
|
||||
|
||||
❌ **We cannot help with:**
|
||||
|
||||
- Custom development for your specific use case
|
||||
- Troubleshooting your specific AI provider issues
|
||||
- General AI or programming tutorials
|
||||
- Commercial support agreements
|
||||
|
||||
## 💪 Contributing Back
|
||||
|
||||
The best way to get help is to help others:
|
||||
|
||||
- Answer questions in discussions
|
||||
- Improve documentation
|
||||
- Share useful patterns
|
||||
- Report bugs clearly
|
||||
- Review pull requests
|
||||
|
||||
See our [Contributing Guide](./CONTRIBUTING.md) for details.
|
||||
|
||||
---
|
||||
|
||||
*Remember: We're all here to make Fabric better. Be kind, be helpful, and let's build something amazing together!*
|
||||
140
docs/Shell-Completions.md
Normal file
140
docs/Shell-Completions.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Shell Completions for Fabric
|
||||
|
||||
Fabric comes with shell completion support for Zsh, Bash, and Fish shells. These completions provide intelligent tab-completion for commands, flags, patterns, models, contexts, and more.
|
||||
|
||||
## Quick Setup (Automated)
|
||||
|
||||
You can install completions without cloning the repo:
|
||||
|
||||
```bash
|
||||
# No-clone install (Zsh/Bash/Fish supported)
|
||||
curl -fsSL https://raw.githubusercontent.com/danielmiessler/Fabric/refs/heads/main/completions/setup-completions.sh | sh
|
||||
|
||||
# Optional: dry-run first
|
||||
curl -fsSL https://raw.githubusercontent.com/danielmiessler/Fabric/refs/heads/main/completions/setup-completions.sh | sh -s -- --dry-run
|
||||
|
||||
# Optional: override the download source
|
||||
FABRIC_COMPLETIONS_BASE_URL="https://raw.githubusercontent.com/danielmiessler/Fabric/refs/heads/main/completions" \
|
||||
sh -c "$(curl -fsSL https://raw.githubusercontent.com/danielmiessler/Fabric/refs/heads/main/completions/setup-completions.sh)"
|
||||
```
|
||||
|
||||
Or, if you have the repository locally:
|
||||
|
||||
```bash
|
||||
# Run the automated setup script from a cloned repo
|
||||
./completions/setup-completions.sh
|
||||
|
||||
# Or see what it would do first
|
||||
./completions/setup-completions.sh --dry-run
|
||||
```
|
||||
|
||||
The script will:
|
||||
|
||||
- Detect whether you have `fabric` or `fabric-ai` installed
|
||||
- Detect your current shell (zsh, bash, or fish)
|
||||
- Use your existing `$fpath` directories (for zsh) or standard completion directories
|
||||
- Install the completion file with the correct name
|
||||
- Provide instructions for enabling the completions
|
||||
|
||||
If the completion files aren't present locally (e.g., when running via `curl`), the script will automatically download them from GitHub.
|
||||
|
||||
For manual installation or troubleshooting, see the detailed instructions below.
|
||||
|
||||
## Manual Installation
|
||||
|
||||
### Zsh
|
||||
|
||||
1. Copy the completion file to a directory in your `$fpath`:
|
||||
|
||||
```bash
|
||||
sudo cp completions/_fabric /usr/local/share/zsh/site-functions/
|
||||
```
|
||||
|
||||
2. **Important**: If you installed fabric as `fabric-ai`, create a symlink so completions work:
|
||||
|
||||
```bash
|
||||
sudo ln -s /usr/local/share/zsh/site-functions/_fabric /usr/local/share/zsh/site-functions/_fabric-ai
|
||||
```
|
||||
|
||||
3. Restart your shell or reload completions:
|
||||
|
||||
```bash
|
||||
autoload -U compinit && compinit
|
||||
```
|
||||
|
||||
### Bash
|
||||
|
||||
1. Copy the completion file to a standard completion directory:
|
||||
|
||||
```bash
|
||||
# System-wide installation
|
||||
sudo cp completions/fabric.bash /etc/bash_completion.d/
|
||||
|
||||
# Or user-specific installation
|
||||
mkdir -p ~/.local/share/bash-completion/completions/
|
||||
cp completions/fabric.bash ~/.local/share/bash-completion/completions/fabric
|
||||
```
|
||||
|
||||
2. **Important**: If you installed fabric as `fabric-ai`, create a symlink:
|
||||
|
||||
```bash
|
||||
# For system-wide installation
|
||||
sudo ln -s /etc/bash_completion.d/fabric.bash /etc/bash_completion.d/fabric-ai.bash
|
||||
|
||||
# Or for user-specific installation
|
||||
ln -s ~/.local/share/bash-completion/completions/fabric ~/.local/share/bash-completion/completions/fabric-ai
|
||||
```
|
||||
|
||||
3. Restart your shell or source the completion:
|
||||
|
||||
```bash
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
### Fish
|
||||
|
||||
1. Copy the completion file to Fish's completion directory:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/fish/completions
|
||||
cp completions/fabric.fish ~/.config/fish/completions/
|
||||
```
|
||||
|
||||
2. **Important**: If you installed fabric as `fabric-ai`, create a symlink:
|
||||
|
||||
```bash
|
||||
ln -s ~/.config/fish/completions/fabric.fish ~/.config/fish/completions/fabric-ai.fish
|
||||
```
|
||||
|
||||
3. Fish will automatically load the completions (no restart needed).
|
||||
|
||||
## Features
|
||||
|
||||
The completions provide intelligent suggestions for:
|
||||
|
||||
- **Patterns**: Tab-complete available patterns with `-p` or `--pattern`
|
||||
- **Models**: Tab-complete available models with `-m` or `--model`
|
||||
- **Contexts**: Tab-complete contexts for context-related flags
|
||||
- **Sessions**: Tab-complete sessions for session-related flags
|
||||
- **Strategies**: Tab-complete available strategies
|
||||
- **Extensions**: Tab-complete registered extensions
|
||||
- **Gemini Voices**: Tab-complete TTS voices for `--voice`
|
||||
- **File paths**: Smart file completion for attachment, output, and config options
|
||||
- **Flag completion**: All available command-line flags and options
|
||||
|
||||
## Alternative Installation Method
|
||||
|
||||
You can also source the completion files directly in your shell's configuration file:
|
||||
|
||||
- **Zsh**: Add to `~/.zshrc`: `source /path/to/fabric/completions/_fabric`
|
||||
- **Bash**: Add to `~/.bashrc`: `source /path/to/fabric/completions/fabric.bash`
|
||||
- **Fish**: The file-based installation method above is preferred for Fish
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- If completions don't work, ensure the completion files have proper permissions
|
||||
- For Zsh, verify that the completion directory is in your `$fpath`
|
||||
- If you renamed the fabric binary, make sure to create the appropriate symlinks as described above
|
||||
- Restart your shell after installation to ensure completions are loaded
|
||||
|
||||
The completion system dynamically queries the fabric command for current patterns, models, and other resources, so your completions will always be up-to-date with your fabric installation.
|
||||
139
docs/Using-Speech-To-Text.md
Normal file
139
docs/Using-Speech-To-Text.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# Using Speech-To-Text (STT) with Fabric
|
||||
|
||||
Fabric supports speech-to-text transcription of audio and video files using OpenAI's transcription models. This feature allows you to convert spoken content into text that can then be processed through Fabric's patterns.
|
||||
|
||||
## Overview
|
||||
|
||||
The STT feature integrates OpenAI's Whisper and GPT-4o transcription models to convert audio/video files into text. The transcribed text is automatically passed as input to your chosen pattern or chat session.
|
||||
|
||||
## Requirements
|
||||
|
||||
- OpenAI API key configured in Fabric
|
||||
- For files larger than 25MB: `ffmpeg` installed on your system
|
||||
- Supported audio/video formats: `.mp3`, `.mp4`, `.mpeg`, `.mpga`, `.m4a`, `.wav`, `.webm`
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Simple Transcription
|
||||
|
||||
To transcribe an audio file and send the result to a pattern:
|
||||
|
||||
```bash
|
||||
fabric --transcribe-file /path/to/audio.mp3 --transcribe-model whisper-1 --pattern summarize
|
||||
```
|
||||
|
||||
### Transcription Only
|
||||
|
||||
To just transcribe a file without applying a pattern:
|
||||
|
||||
```bash
|
||||
fabric --transcribe-file /path/to/audio.mp3 --transcribe-model whisper-1
|
||||
```
|
||||
|
||||
## Command Line Flags
|
||||
|
||||
### Required Flags
|
||||
|
||||
- `--transcribe-file`: Path to the audio or video file to transcribe
|
||||
- `--transcribe-model`: Model to use for transcription (required when using transcription)
|
||||
|
||||
### Optional Flags
|
||||
|
||||
- `--split-media-file`: Automatically split files larger than 25MB into chunks using ffmpeg
|
||||
|
||||
## Available Models
|
||||
|
||||
You can list all available transcription models with:
|
||||
|
||||
```bash
|
||||
fabric --list-transcription-models
|
||||
```
|
||||
|
||||
Currently supported models:
|
||||
|
||||
- `whisper-1`: OpenAI's Whisper model
|
||||
- `gpt-4o-mini-transcribe`: GPT-4o Mini transcription model
|
||||
- `gpt-4o-transcribe`: GPT-4o transcription model
|
||||
|
||||
## File Size Handling
|
||||
|
||||
### Files Under 25MB
|
||||
|
||||
Files under the 25MB limit are processed directly without any special handling.
|
||||
|
||||
### Files Over 25MB
|
||||
|
||||
For files exceeding OpenAI's 25MB limit, you have two options:
|
||||
|
||||
1. **Manual handling**: The command will fail with an error message suggesting to use `--split-media-file`
|
||||
2. **Automatic splitting**: Use the `--split-media-file` flag to automatically split the file into chunks
|
||||
|
||||
```bash
|
||||
fabric --transcribe-file large_recording.mp4 --transcribe-model whisper-1 --split-media-file --pattern summarize
|
||||
```
|
||||
|
||||
When splitting is enabled:
|
||||
|
||||
- Fabric uses `ffmpeg` to split the file into 10-minute segments initially
|
||||
- If segments are still too large, it reduces the segment time by half repeatedly
|
||||
- All segments are transcribed and the results are concatenated
|
||||
- Temporary files are automatically cleaned up after processing
|
||||
|
||||
## Integration with Patterns
|
||||
|
||||
The transcribed text is seamlessly integrated into Fabric's workflow:
|
||||
|
||||
1. File is transcribed using the specified model
|
||||
2. Transcribed text becomes the input message
|
||||
3. Text is sent to the specified pattern or chat session
|
||||
|
||||
### Example Workflows
|
||||
|
||||
**Meeting transcription and summarization:**
|
||||
|
||||
```bash
|
||||
fabric --transcribe-file meeting.mp4 --transcribe-model gpt-4o-transcribe --pattern summarize
|
||||
```
|
||||
|
||||
**Interview analysis:**
|
||||
|
||||
```bash
|
||||
fabric --transcribe-file interview.mp3 --transcribe-model whisper-1 --pattern extract_insights
|
||||
```
|
||||
|
||||
**Large video file processing:**
|
||||
|
||||
```bash
|
||||
fabric --transcribe-file presentation.mp4 --transcribe-model gpt-4o-transcribe --split-media-file --pattern create_summary
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
Common error scenarios:
|
||||
|
||||
- **Unsupported format**: Only the listed audio/video formats are supported
|
||||
- **File too large**: Use `--split-media-file` for files over 25MB
|
||||
- **Missing ffmpeg**: Install ffmpeg for automatic file splitting
|
||||
- **Invalid model**: Use `--list-transcription-models` to see available models
|
||||
- **Missing model**: The `--transcribe-model` flag is required when using `--transcribe-file`
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Implementation
|
||||
|
||||
- Transcription is handled in `internal/cli/transcribe.go:14`
|
||||
- OpenAI-specific implementation in `internal/plugins/ai/openai/openai_audio.go:41`
|
||||
- File splitting uses ffmpeg with configurable segment duration
|
||||
- Supports any vendor that implements the `transcriber` interface
|
||||
|
||||
### Processing Pipeline
|
||||
|
||||
1. CLI validates file format and size
|
||||
2. If file > 25MB and splitting enabled, file is split using ffmpeg
|
||||
3. Each file/segment is sent to OpenAI's transcription API
|
||||
4. Results are concatenated with spaces between segments
|
||||
5. Transcribed text is passed as input to the main Fabric pipeline
|
||||
|
||||
### Vendor Support
|
||||
|
||||
Currently, only OpenAI is supported for transcription, but the interface allows for future expansion to other vendors that provide transcription capabilities.
|
||||
298
docs/YouTube-Processing.md
Normal file
298
docs/YouTube-Processing.md
Normal file
@@ -0,0 +1,298 @@
|
||||
# YouTube Processing with Fabric
|
||||
|
||||
Fabric provides powerful YouTube video processing capabilities that allow you to extract transcripts, comments, and metadata from YouTube videos and playlists. This guide covers all the available options and common use cases.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **yt-dlp**: Required for transcript extraction. Install on MacOS with:
|
||||
|
||||
```bash
|
||||
brew install yt-dlp
|
||||
```
|
||||
|
||||
Or use the package manager of your choice for your operating system.
|
||||
|
||||
See the [yt-dlp wiki page](https://github.com/yt-dlp/yt-dlp/wiki/Installation) for your specific installation instructions.
|
||||
|
||||
- **YouTube API Key** (optional): Only needed for comments and metadata extraction. Configure with:
|
||||
|
||||
```bash
|
||||
fabric --setup
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Extract Transcript
|
||||
|
||||
Extract a video transcript and process it with a pattern:
|
||||
|
||||
```bash
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --pattern summarize
|
||||
```
|
||||
|
||||
### Extract Transcript with Timestamps
|
||||
|
||||
Get transcript with timestamps preserved:
|
||||
|
||||
```bash
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --transcript-with-timestamps --pattern extract_wisdom
|
||||
```
|
||||
|
||||
### Extract Comments
|
||||
|
||||
Get video comments (requires YouTube API key):
|
||||
|
||||
```bash
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --comments --pattern analyze_claims
|
||||
```
|
||||
|
||||
### Extract Metadata
|
||||
|
||||
Get video metadata as JSON:
|
||||
|
||||
```bash
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --metadata
|
||||
```
|
||||
|
||||
## Advanced Options
|
||||
|
||||
### Custom yt-dlp Arguments
|
||||
|
||||
Pass additional arguments to yt-dlp for advanced functionality. **User-provided arguments take precedence** over built-in fabric arguments, giving you full control:
|
||||
|
||||
```bash
|
||||
# Use browser cookies for age-restricted or private videos
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --yt-dlp-args="--cookies-from-browser brave"
|
||||
|
||||
# Override language selection (takes precedence over -g flag)
|
||||
fabric -g en -y "https://www.youtube.com/watch?v=VIDEO_ID" --yt-dlp-args="--sub-langs es,fr"
|
||||
|
||||
# Use specific format
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --yt-dlp-args="--format best"
|
||||
|
||||
# Handle rate limiting (slow down requests)
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --yt-dlp-args="--sleep-requests 1"
|
||||
|
||||
# Multiple arguments (use quotes)
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --yt-dlp-args="--cookies-from-browser firefox --write-info-json"
|
||||
|
||||
# Combine rate limiting with authentication
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --yt-dlp-args="--cookies-from-browser brave --sleep-requests 1"
|
||||
|
||||
# Override subtitle format (takes precedence over built-in --sub-format vtt)
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --yt-dlp-args="--sub-format srt"
|
||||
```
|
||||
|
||||
#### Argument Precedence
|
||||
|
||||
Fabric constructs the yt-dlp command in this order:
|
||||
|
||||
1. **Built-in base arguments** (`--write-auto-subs`, `--skip-download`, etc.)
|
||||
2. **Language selection** (from `-g` flag): `--sub-langs LANGUAGE`
|
||||
3. **User arguments** (from `--yt-dlp-args`): **These override any conflicting built-in arguments**
|
||||
4. **Video URL**
|
||||
|
||||
This means you can override any built-in behavior by specifying it in `--yt-dlp-args`.
|
||||
|
||||
### Playlist Processing
|
||||
|
||||
Process entire playlists:
|
||||
|
||||
```bash
|
||||
# Process all videos in a playlist
|
||||
fabric -y "https://www.youtube.com/playlist?list=PLAYLIST_ID" --playlist --pattern summarize
|
||||
|
||||
# Save playlist videos to CSV
|
||||
fabric -y "https://www.youtube.com/playlist?list=PLAYLIST_ID" --playlist -o playlist.csv
|
||||
```
|
||||
|
||||
### Language Support
|
||||
|
||||
Specify transcript language:
|
||||
|
||||
```bash
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" -g es --pattern translate
|
||||
```
|
||||
|
||||
## Combining Options
|
||||
|
||||
You can combine multiple YouTube processing options:
|
||||
|
||||
```bash
|
||||
# Get transcript, comments, and metadata
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" \
|
||||
--transcript \
|
||||
--comments \
|
||||
--metadata \
|
||||
--pattern comprehensive_analysis
|
||||
```
|
||||
|
||||
## Output Options
|
||||
|
||||
### Save to File
|
||||
|
||||
```bash
|
||||
# Save output to file
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --pattern summarize -o summary.md
|
||||
|
||||
# Save entire session including input
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --pattern summarize --output-session -o full_session.md
|
||||
```
|
||||
|
||||
### Stream Output
|
||||
|
||||
Get real-time streaming output:
|
||||
|
||||
```bash
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --pattern summarize --stream
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Content Analysis
|
||||
|
||||
```bash
|
||||
# Analyze video content for key insights
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --pattern extract_wisdom
|
||||
|
||||
# Check claims made in the video
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --pattern analyze_claims
|
||||
```
|
||||
|
||||
### Educational Content
|
||||
|
||||
```bash
|
||||
# Create study notes from educational videos
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --pattern create_study_notes
|
||||
|
||||
# Extract key concepts and definitions
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --pattern extract_concepts
|
||||
```
|
||||
|
||||
### Meeting/Conference Processing
|
||||
|
||||
```bash
|
||||
# Summarize conference talks with timestamps
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" \
|
||||
--transcript-with-timestamps \
|
||||
--pattern meeting_summary
|
||||
|
||||
# Extract action items from recorded meetings
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --pattern extract_action_items
|
||||
```
|
||||
|
||||
### Content Creation
|
||||
|
||||
```bash
|
||||
# Create social media posts from video content
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --pattern create_social_posts
|
||||
|
||||
# Generate blog post from video transcript
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" --pattern write_blog_post
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **"yt-dlp not found"**: Install yt-dlp using pip or your package manager
|
||||
2. **Age-restricted videos**: Use `--yt-dlp-args="--cookies-from-browser BROWSER"`
|
||||
3. **No subtitles available**: Some videos don't have auto-generated subtitles
|
||||
4. **API rate limits**: YouTube API has daily quotas for comments/metadata
|
||||
5. **HTTP 429 errors**: YouTube is rate limiting subtitle requests
|
||||
|
||||
### Error Messages
|
||||
|
||||
- **"YouTube is not configured"**: Run `fabric --setup` to configure YouTube API
|
||||
- **"yt-dlp failed"**: Check video URL and try with `--yt-dlp-args` for authentication
|
||||
- **"No transcript content found"**: Video may not have subtitles available
|
||||
- **"HTTP Error 429: Too Many Requests"**: YouTube rate limit exceeded. This is increasingly common. Solutions:
|
||||
- **Wait 10-30 minutes and try again** (most effective)
|
||||
- Use longer sleep: `--yt-dlp-args="--sleep-requests 5"`
|
||||
- Try with browser cookies: `--yt-dlp-args="--cookies-from-browser brave --sleep-requests 5"`
|
||||
- **Try a different video** - some videos are less restricted
|
||||
- **Use a VPN** - different IP address may help
|
||||
- **Try without language specification** - let yt-dlp choose any available language
|
||||
- **Try English instead** - `fabric -g en` (English subtitles may be less rate-limited)
|
||||
|
||||
### Language Fallback Behavior
|
||||
|
||||
When you specify a language (e.g., `-g es` for Spanish) but that language isn't available or fails to download:
|
||||
|
||||
1. **Automatic fallback**: Fabric automatically retries without language specification
|
||||
2. **Smart file detection**: If the fallback downloads a different language (e.g., English), Fabric will automatically detect and use it
|
||||
3. **No manual intervention needed**: The process is transparent to the user
|
||||
|
||||
```bash
|
||||
# Even if Spanish isn't available, this will work with whatever language yt-dlp finds
|
||||
fabric -g es -y "https://youtube.com/watch?v=VIDEO_ID" --pattern summarize
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### YAML Configuration
|
||||
|
||||
You can set default yt-dlp arguments in your config file (`~/.config/fabric/config.yaml`):
|
||||
|
||||
```yaml
|
||||
ytDlpArgs: "--cookies-from-browser brave --write-info-json"
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Set up your YouTube API key:
|
||||
|
||||
```bash
|
||||
export FABRIC_YOUTUBE_API_KEY="your_api_key_here"
|
||||
```
|
||||
|
||||
## Tips and Best Practices
|
||||
|
||||
1. **Use specific patterns**: Choose patterns that match your use case for better results
|
||||
2. **Combine with other tools**: Pipe output to other commands or save to files for further processing
|
||||
3. **Batch processing**: Use playlists to process multiple videos efficiently
|
||||
4. **Authentication**: Use browser cookies for accessing private or age-restricted content
|
||||
5. **Language support**: Specify language codes for better transcript accuracy
|
||||
6. **Rate limiting**: If you encounter 429 errors, use `--sleep-requests 1` to slow down requests
|
||||
7. **Persistent settings**: Set common yt-dlp args in your config file to avoid repeating them
|
||||
8. **Argument precedence**: Use `--yt-dlp-args` to override any built-in behavior when needed
|
||||
9. **Testing**: Use `yt-dlp --list-subs URL` to see available subtitle languages before processing
|
||||
|
||||
## Examples
|
||||
|
||||
### Quick Video Summary
|
||||
|
||||
```bash
|
||||
fabric -y "https://www.youtube.com/watch?v=dQw4w9WgXcQ" --pattern summarize --stream
|
||||
```
|
||||
|
||||
### Detailed Analysis with Authentication
|
||||
|
||||
```bash
|
||||
fabric -y "https://www.youtube.com/watch?v=VIDEO_ID" \
|
||||
--yt-dlp-args="--cookies-from-browser chrome" \
|
||||
--transcript-with-timestamps \
|
||||
--comments \
|
||||
--pattern comprehensive_analysis \
|
||||
-o analysis.md
|
||||
```
|
||||
|
||||
### Playlist Processing
|
||||
|
||||
```bash
|
||||
fabric -y "https://www.youtube.com/playlist?list=PLrAXtmRdnEQy6nuLvVUxpDnx4C0823vBN" \
|
||||
--playlist \
|
||||
--pattern extract_wisdom \
|
||||
-o playlist_wisdom.md
|
||||
```
|
||||
|
||||
### Override Built-in Language Selection
|
||||
|
||||
```bash
|
||||
# Built-in language selection (-g es) is overridden by user args
|
||||
fabric -g es -y "https://www.youtube.com/watch?v=VIDEO_ID" \
|
||||
--yt-dlp-args="--sub-langs fr,de,en" \
|
||||
--pattern translate
|
||||
```
|
||||
|
||||
For more patterns and advanced usage, see the main [Fabric documentation](../README.md).
|
||||
BIN
docs/images/svelte-preview.png
Normal file
BIN
docs/images/svelte-preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
21
docs/notification-config.yaml
Normal file
21
docs/notification-config.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
# Example Fabric configuration with notification support
|
||||
# Save this to ~/.config/fabric/config.yaml to use as defaults
|
||||
|
||||
# Enable notifications by default for all commands
|
||||
notification: true
|
||||
|
||||
# Optional: Use a custom notification command
|
||||
# Examples:
|
||||
# macOS with custom sound:
|
||||
# notificationCommand: 'osascript -e "display notification \"$2\" with title \"$1\" sound name \"Ping\""'
|
||||
#
|
||||
# Linux with custom urgency:
|
||||
# notificationCommand: 'notify-send --urgency=normal "$1" "$2"'
|
||||
#
|
||||
# Custom script:
|
||||
# notificationCommand: '/path/to/custom-notification-script.sh "$1" "$2"'
|
||||
|
||||
# Other common settings
|
||||
model: "gpt-4o"
|
||||
temperature: 0.7
|
||||
stream: true
|
||||
36
docs/voices/README.md
Normal file
36
docs/voices/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Voice Samples
|
||||
|
||||
This directory contains sample audio files demonstrating different Gemini TTS voices.
|
||||
|
||||
## Sample Files
|
||||
|
||||
Each voice sample says "The quick brown fox jumped over the lazy dog" to demonstrate the voice characteristics:
|
||||
|
||||
- **Kore.wav** - Firm and confident (default voice)
|
||||
- **Charon.wav** - Informative and clear
|
||||
- **Vega.wav** - Smooth and pleasant
|
||||
- **Capella.wav** - Warm and welcoming
|
||||
- **Achird.wav** - Friendly and approachable
|
||||
- **Lyra.wav** - Melodic and expressive
|
||||
|
||||
## Generating Samples
|
||||
|
||||
To generate these samples, use the following commands:
|
||||
|
||||
```bash
|
||||
# Generate each voice sample
|
||||
echo "The quick brown fox jumped over the lazy dog" | fabric -m gemini-2.5-flash-preview-tts --voice Kore -o docs/voices/Kore.wav
|
||||
echo "The quick brown fox jumped over the lazy dog" | fabric -m gemini-2.5-flash-preview-tts --voice Charon -o docs/voices/Charon.wav
|
||||
echo "The quick brown fox jumped over the lazy dog" | fabric -m gemini-2.5-flash-preview-tts --voice Vega -o docs/voices/Vega.wav
|
||||
echo "The quick brown fox jumped over the lazy dog" | fabric -m gemini-2.5-flash-preview-tts --voice Capella -o docs/voices/Capella.wav
|
||||
echo "The quick brown fox jumped over the lazy dog" | fabric -m gemini-2.5-flash-preview-tts --voice Achird -o docs/voices/Achird.wav
|
||||
echo "The quick brown fox jumped over the lazy dog" | fabric -m gemini-2.5-flash-preview-tts --voice Lyra -o docs/voices/Lyra.wav
|
||||
```
|
||||
|
||||
## Audio Format
|
||||
|
||||
- **Format**: WAV (uncompressed)
|
||||
- **Sample Rate**: 24kHz
|
||||
- **Bit Depth**: 16-bit
|
||||
- **Channels**: Mono
|
||||
- **Approximate Size**: ~500KB per sample
|
||||
18
go.mod
18
go.mod
@@ -5,7 +5,7 @@ go 1.24.0
|
||||
toolchain go1.24.2
|
||||
|
||||
require (
|
||||
github.com/anthropics/anthropic-sdk-go v1.4.0
|
||||
github.com/anthropics/anthropic-sdk-go v1.9.1
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.4
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.27
|
||||
@@ -15,13 +15,13 @@ require (
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/go-git/go-git/v5 v5.16.2
|
||||
github.com/go-shiori/go-readability v0.0.0-20250217085726-9f5bf5ca7612
|
||||
github.com/google/generative-ai-go v0.20.1
|
||||
github.com/google/go-github/v66 v66.0.0
|
||||
github.com/hasura/go-graphql-client v0.14.4
|
||||
github.com/jessevdk/go-flags v1.6.1
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/mattn/go-sqlite3 v1.14.28
|
||||
github.com/ollama/ollama v0.9.0
|
||||
github.com/ollama/ollama v0.11.7
|
||||
github.com/openai/openai-go v1.8.2
|
||||
github.com/otiai10/copy v1.14.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
@@ -35,13 +35,16 @@ require (
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.121.2 // indirect
|
||||
cloud.google.com/go/ai v0.12.1 // indirect
|
||||
cloud.google.com/go/auth v0.16.2 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.7.0 // indirect
|
||||
cloud.google.com/go/longrunning v0.6.7 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
@@ -109,18 +112,17 @@ require (
|
||||
github.com/ugorji/go/codec v1.2.14 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.36.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||
golang.org/x/arch v0.18.0 // indirect
|
||||
golang.org/x/crypto v0.39.0 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
google.golang.org/genai v1.17.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/grpc v1.73.0 // indirect
|
||||
|
||||
32
go.sum
32
go.sum
@@ -1,15 +1,11 @@
|
||||
cloud.google.com/go v0.121.2 h1:v2qQpN6Dx9x2NmwrqlesOt3Ys4ol5/lFZ6Mg1B7OJCg=
|
||||
cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
|
||||
cloud.google.com/go/ai v0.12.1 h1:m1n/VjUuHS+pEO/2R4/VbuuEIkgk0w67fDQvFaMngM0=
|
||||
cloud.google.com/go/ai v0.12.1/go.mod h1:5vIPNe1ZQsVZqCliXIPL4QnhObQQY4d9hAGHdVc4iw4=
|
||||
cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=
|
||||
cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
|
||||
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
|
||||
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
|
||||
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
@@ -21,8 +17,8 @@ github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kk
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/anthropics/anthropic-sdk-go v1.4.0 h1:fU1jKxYbQdQDiEXCxeW5XZRIOwKevn/PMg8Ay1nnUx0=
|
||||
github.com/anthropics/anthropic-sdk-go v1.4.0/go.mod h1:AapDW22irxK2PSumZiQXYUFvsdQgkwIWlpESweWZI/c=
|
||||
github.com/anthropics/anthropic-sdk-go v1.9.1 h1:raRhZKmayVSVZtLpLDd6IsMXvxLeeSU03/2IBTerWlg=
|
||||
github.com/anthropics/anthropic-sdk-go v1.9.1/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
@@ -126,8 +122,6 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8J
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/generative-ai-go v0.20.1 h1:6dEIujpgN2V0PgLhr6c/M1ynRdc7ARtiIDPFzj45uNQ=
|
||||
github.com/google/generative-ai-go v0.20.1/go.mod h1:TjOnZJmZKzarWbjUJgy+r3Ee7HGBRVLhOIgupnwR4Bg=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
@@ -145,6 +139,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
|
||||
github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hasura/go-graphql-client v0.14.4 h1:bYU7/+V50T2YBGdNQXt6l4f2cMZPECPUd8cyCR+ixtw=
|
||||
github.com/hasura/go-graphql-client v0.14.4/go.mod h1:jfSZtBER3or+88Q9vFhWHiFMPppfYILRyl+0zsgPIIw=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
@@ -157,6 +153,8 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
@@ -182,8 +180,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/ollama/ollama v0.9.0 h1:GvdGhi8G/QMnFrY0TMLDy1bXua+Ify8KTkFe4ZY/OZs=
|
||||
github.com/ollama/ollama v0.9.0/go.mod h1:aio9yQ7nc4uwIbn6S0LkGEPgn8/9bNQLL1nHuH+OcD0=
|
||||
github.com/ollama/ollama v0.11.7 h1:CuYjaJ/YEnvLDpJocJbbVdpdVFyGA/OP6lKFyzZD4dI=
|
||||
github.com/ollama/ollama v0.11.7/go.mod h1:9+1//yWPsDE2u+l1a5mpaKrYw4VdnSsRU3ioq5BvMms=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/openai/openai-go v1.8.2 h1:UqSkJ1vCOPUpz9Ka5tS0324EJFEuOvMc+lA/EarJWP8=
|
||||
@@ -249,8 +247,6 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||
@@ -272,8 +268,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b h1:QoALfVG9rhQ/M7vYDScfPdWjGL9dlsVVM5VGh7aKoAA=
|
||||
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
@@ -331,8 +327,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -345,8 +341,6 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
@@ -357,6 +351,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.236.0 h1:CAiEiDVtO4D/Qja2IA9VzlFrgPnK3XVMmRoJZlSWbc0=
|
||||
google.golang.org/api v0.236.0/go.mod h1:X1WF9CU2oTc+Jml1tiIxGmWFK/UZezdqEu09gcxZAj4=
|
||||
google.golang.org/genai v1.17.0 h1:lXYSnWShPYjxTouxRj0zF8RsNmSF+SKo7SQ7dM35NlI=
|
||||
google.golang.org/genai v1.17.0/go.mod h1:QPj5NGJw+3wEOHg+PrsWwJKvG6UC84ex5FR7qAYsN/M=
|
||||
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
|
||||
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
|
||||
|
||||
@@ -3,11 +3,15 @@ package cli
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/core"
|
||||
"github.com/danielmiessler/fabric/internal/domain"
|
||||
debuglog "github.com/danielmiessler/fabric/internal/log"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/db/fsdb"
|
||||
"github.com/danielmiessler/fabric/internal/tools/notifications"
|
||||
)
|
||||
|
||||
// handleChatProcessing handles the main chat processing logic
|
||||
@@ -15,10 +19,23 @@ func handleChatProcessing(currentFlags *Flags, registry *core.PluginRegistry, me
|
||||
if messageTools != "" {
|
||||
currentFlags.AppendMessage(messageTools)
|
||||
}
|
||||
// Check for pattern-specific model via environment variable
|
||||
if currentFlags.Pattern != "" && currentFlags.Model == "" {
|
||||
envVar := "FABRIC_MODEL_" + strings.ToUpper(strings.ReplaceAll(currentFlags.Pattern, "-", "_"))
|
||||
if modelSpec := os.Getenv(envVar); modelSpec != "" {
|
||||
parts := strings.SplitN(modelSpec, "|", 2)
|
||||
if len(parts) == 2 {
|
||||
currentFlags.Vendor = parts[0]
|
||||
currentFlags.Model = parts[1]
|
||||
} else {
|
||||
currentFlags.Model = modelSpec
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var chatter *core.Chatter
|
||||
if chatter, err = registry.GetChatter(currentFlags.Model, currentFlags.ModelContextLength,
|
||||
currentFlags.Strategy, currentFlags.Stream, currentFlags.DryRun); err != nil {
|
||||
currentFlags.Vendor, currentFlags.Strategy, currentFlags.Stream, currentFlags.DryRun); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -35,6 +52,40 @@ func handleChatProcessing(currentFlags *Flags, registry *core.PluginRegistry, me
|
||||
if chatOptions, err = currentFlags.BuildChatOptions(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user is requesting audio output or using a TTS model
|
||||
isAudioOutput := currentFlags.Output != "" && IsAudioFormat(currentFlags.Output)
|
||||
isTTSModel := isTTSModel(currentFlags.Model)
|
||||
|
||||
if isTTSModel && !isAudioOutput {
|
||||
err = fmt.Errorf("TTS model '%s' requires audio output. Please specify an audio output file with -o flag (e.g., -o output.wav)", currentFlags.Model)
|
||||
return
|
||||
}
|
||||
|
||||
if isAudioOutput && !isTTSModel {
|
||||
err = fmt.Errorf("audio output file '%s' specified but model '%s' is not a TTS model. Please use a TTS model like gemini-2.5-flash-preview-tts", currentFlags.Output, currentFlags.Model)
|
||||
return
|
||||
}
|
||||
|
||||
// For TTS models, check if output file already exists BEFORE processing
|
||||
if isTTSModel && isAudioOutput {
|
||||
outputFile := currentFlags.Output
|
||||
// Add .wav extension if not provided
|
||||
if filepath.Ext(outputFile) == "" {
|
||||
outputFile += ".wav"
|
||||
}
|
||||
if _, err = os.Stat(outputFile); err == nil {
|
||||
err = fmt.Errorf("file %s already exists. Please choose a different filename or remove the existing file", outputFile)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Set audio options in chat config
|
||||
chatOptions.AudioOutput = isAudioOutput
|
||||
if isAudioOutput {
|
||||
chatOptions.AudioFormat = "wav" // Default to WAV format
|
||||
}
|
||||
|
||||
if session, err = chatter.Send(chatReq, chatOptions); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -42,8 +93,13 @@ func handleChatProcessing(currentFlags *Flags, registry *core.PluginRegistry, me
|
||||
result := session.GetLastMessage().Content
|
||||
|
||||
if !currentFlags.Stream || currentFlags.SuppressThink {
|
||||
// print the result if it was not streamed already or suppress-think disabled streaming output
|
||||
fmt.Println(result)
|
||||
// For TTS models with audio output, show a user-friendly message instead of raw data
|
||||
if isTTSModel && isAudioOutput && strings.HasPrefix(result, "FABRIC_AUDIO_DATA:") {
|
||||
fmt.Printf("TTS audio generated successfully and saved to: %s\n", currentFlags.Output)
|
||||
} else {
|
||||
// print the result if it was not streamed already or suppress-think disabled streaming output
|
||||
fmt.Println(result)
|
||||
}
|
||||
}
|
||||
|
||||
// if the copy flag is set, copy the message to the clipboard
|
||||
@@ -59,8 +115,85 @@ func handleChatProcessing(currentFlags *Flags, registry *core.PluginRegistry, me
|
||||
sessionAsString := session.String()
|
||||
err = CreateOutputFile(sessionAsString, currentFlags.Output)
|
||||
} else {
|
||||
err = CreateOutputFile(result, currentFlags.Output)
|
||||
// For TTS models, we need to handle audio output differently
|
||||
if isTTSModel && isAudioOutput {
|
||||
// Check if result contains actual audio data
|
||||
if strings.HasPrefix(result, "FABRIC_AUDIO_DATA:") {
|
||||
// Extract the binary audio data
|
||||
audioData := result[len("FABRIC_AUDIO_DATA:"):]
|
||||
err = CreateAudioOutputFile([]byte(audioData), currentFlags.Output)
|
||||
} else {
|
||||
// Fallback for any error messages or unexpected responses
|
||||
err = CreateOutputFile(result, currentFlags.Output)
|
||||
}
|
||||
} else {
|
||||
err = CreateOutputFile(result, currentFlags.Output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send notification if requested
|
||||
if chatOptions.Notification {
|
||||
if err = sendNotification(chatOptions, chatReq.PatternName, result); err != nil {
|
||||
// Log notification error but don't fail the main command
|
||||
debuglog.Log("Failed to send notification: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// sendNotification sends a desktop notification about command completion.
|
||||
//
|
||||
// When truncating the result for notification display, this function counts Unicode code points,
|
||||
// not grapheme clusters. As a result, complex emoji or accented characters with multiple combining
|
||||
// characters may be truncated improperly. This is a limitation of the current implementation.
|
||||
func sendNotification(options *domain.ChatOptions, patternName, result string) error {
|
||||
title := "Fabric Command Complete"
|
||||
if patternName != "" {
|
||||
title = fmt.Sprintf("Fabric: %s Complete", patternName)
|
||||
}
|
||||
|
||||
// Limit message length for notification display (counts Unicode code points)
|
||||
message := "Command completed successfully"
|
||||
if result != "" {
|
||||
maxLength := 100
|
||||
runes := []rune(result)
|
||||
if len(runes) > maxLength {
|
||||
message = fmt.Sprintf("Output: %s...", string(runes[:maxLength]))
|
||||
} else {
|
||||
message = fmt.Sprintf("Output: %s", result)
|
||||
}
|
||||
// Clean up newlines for notification display
|
||||
message = strings.ReplaceAll(message, "\n", " ")
|
||||
}
|
||||
|
||||
// Use custom notification command if provided
|
||||
if options.NotificationCommand != "" {
|
||||
// SECURITY: Pass title and message as proper shell positional arguments $1 and $2
|
||||
// This matches the documented interface where custom commands receive title and message as shell variables
|
||||
cmd := exec.Command("sh", "-c", options.NotificationCommand+" \"$1\" \"$2\"", "--", title, message)
|
||||
|
||||
// For debugging: capture and display output from custom commands
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// Use built-in notification system
|
||||
notificationManager := notifications.NewNotificationManager()
|
||||
if !notificationManager.IsAvailable() {
|
||||
return fmt.Errorf("no notification system available")
|
||||
}
|
||||
|
||||
return notificationManager.Send(title, message)
|
||||
}
|
||||
|
||||
// isTTSModel checks if the model is a text-to-speech model
|
||||
func isTTSModel(modelName string) bool {
|
||||
lowerModel := strings.ToLower(modelName)
|
||||
return strings.Contains(lowerModel, "tts") ||
|
||||
strings.Contains(lowerModel, "preview-tts") ||
|
||||
strings.Contains(lowerModel, "text-to-speech")
|
||||
}
|
||||
|
||||
166
internal/cli/chat_test.go
Normal file
166
internal/cli/chat_test.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/domain"
|
||||
)
|
||||
|
||||
func TestSendNotification_SecurityEscaping(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
message string
|
||||
command string
|
||||
expectError bool
|
||||
description string
|
||||
}{
|
||||
{
|
||||
name: "Normal content",
|
||||
title: "Test Title",
|
||||
message: "Test message content",
|
||||
command: `echo "Title: $1, Message: $2"`,
|
||||
expectError: false,
|
||||
description: "Normal content should work fine",
|
||||
},
|
||||
{
|
||||
name: "Content with backticks",
|
||||
title: "Test Title",
|
||||
message: "Test `whoami` injection",
|
||||
command: `echo "Title: $1, Message: $2"`,
|
||||
expectError: false,
|
||||
description: "Backticks should be escaped and not executed",
|
||||
},
|
||||
{
|
||||
name: "Content with semicolon injection",
|
||||
title: "Test Title",
|
||||
message: "Test; echo INJECTED; echo end",
|
||||
command: `echo "Title: $1, Message: $2"`,
|
||||
expectError: false,
|
||||
description: "Semicolon injection should be prevented",
|
||||
},
|
||||
{
|
||||
name: "Content with command substitution",
|
||||
title: "Test Title",
|
||||
message: "Test $(whoami) injection",
|
||||
command: `echo "Title: $1, Message: $2"`,
|
||||
expectError: false,
|
||||
description: "Command substitution should be escaped",
|
||||
},
|
||||
{
|
||||
name: "Content with quote injection",
|
||||
title: "Test Title",
|
||||
message: "Test ' || echo INJECTED || echo ' end",
|
||||
command: `echo "Title: $1, Message: $2"`,
|
||||
expectError: false,
|
||||
description: "Quote injection should be prevented",
|
||||
},
|
||||
{
|
||||
name: "Content with newlines",
|
||||
title: "Test Title",
|
||||
message: "Line 1\nLine 2\nLine 3",
|
||||
command: `echo "Title: $1, Message: $2"`,
|
||||
expectError: false,
|
||||
description: "Newlines should be handled safely",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
options := &domain.ChatOptions{
|
||||
NotificationCommand: tt.command,
|
||||
Notification: true,
|
||||
}
|
||||
|
||||
// This test mainly verifies that the function doesn't panic
|
||||
// and properly escapes dangerous content. The actual command
|
||||
// execution is tested separately in integration tests.
|
||||
err := sendNotification(options, "test_pattern", tt.message)
|
||||
|
||||
if tt.expectError && err == nil {
|
||||
t.Errorf("Expected error for %s, but got none", tt.description)
|
||||
}
|
||||
|
||||
if !tt.expectError && err != nil {
|
||||
t.Errorf("Unexpected error for %s: %v", tt.description, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendNotification_TitleGeneration(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
patternName string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "No pattern name",
|
||||
patternName: "",
|
||||
expected: "Fabric Command Complete",
|
||||
},
|
||||
{
|
||||
name: "With pattern name",
|
||||
patternName: "summarize",
|
||||
expected: "Fabric: summarize Complete",
|
||||
},
|
||||
{
|
||||
name: "Pattern with special characters",
|
||||
patternName: "test_pattern-v2",
|
||||
expected: "Fabric: test_pattern-v2 Complete",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
options := &domain.ChatOptions{
|
||||
NotificationCommand: `echo "Title: $1"`,
|
||||
Notification: true,
|
||||
}
|
||||
|
||||
// We're testing the title generation logic
|
||||
// The actual notification command would echo the title
|
||||
err := sendNotification(options, tt.patternName, "test message")
|
||||
|
||||
// The function should not error for valid inputs
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendNotification_MessageTruncation(t *testing.T) {
|
||||
longMessage := strings.Repeat("A", 150) // 150 characters
|
||||
shortMessage := "Short message"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
message string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "Short message",
|
||||
message: shortMessage,
|
||||
},
|
||||
{
|
||||
name: "Long message truncation",
|
||||
message: longMessage,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
options := &domain.ChatOptions{
|
||||
NotificationCommand: `echo "Message: $2"`,
|
||||
Notification: true,
|
||||
}
|
||||
|
||||
err := sendNotification(options, "test", tt.message)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,11 @@ package cli
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/core"
|
||||
debuglog "github.com/danielmiessler/fabric/internal/log"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/openai"
|
||||
"github.com/danielmiessler/fabric/internal/tools/converter"
|
||||
"github.com/danielmiessler/fabric/internal/tools/youtube"
|
||||
)
|
||||
@@ -18,6 +19,12 @@ func Cli(version string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if currentFlags.Setup {
|
||||
if err = ensureEnvFile(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if currentFlags.Version {
|
||||
fmt.Println(version)
|
||||
return
|
||||
@@ -27,7 +34,7 @@ func Cli(version string) (err error) {
|
||||
var registry, err2 = initializeFabric()
|
||||
if err2 != nil {
|
||||
if !currentFlags.Setup {
|
||||
fmt.Fprintln(os.Stderr, err2.Error())
|
||||
debuglog.Log("%s\n", err2.Error())
|
||||
currentFlags.Setup = true
|
||||
}
|
||||
// Return early if registry is nil to prevent panics in subsequent handlers
|
||||
@@ -36,6 +43,11 @@ func Cli(version string) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Configure OpenAI Responses API setting based on CLI flag
|
||||
if registry != nil {
|
||||
configureOpenAIResponsesAPI(registry, currentFlags.DisableResponsesAPI)
|
||||
}
|
||||
|
||||
// Handle setup and server commands
|
||||
var handled bool
|
||||
if handled, err = handleSetupAndServerCommands(currentFlags, registry, version); err != nil || handled {
|
||||
@@ -62,6 +74,15 @@ func Cli(version string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle transcription if specified
|
||||
if currentFlags.TranscribeFile != "" {
|
||||
var transcriptionMessage string
|
||||
if transcriptionMessage, err = handleTranscription(currentFlags, registry); err != nil {
|
||||
return
|
||||
}
|
||||
currentFlags.Message = AppendMessage(currentFlags.Message, transcriptionMessage)
|
||||
}
|
||||
|
||||
// Process HTML readability if needed
|
||||
if currentFlags.HtmlReadability {
|
||||
if msg, cleanErr := converter.HtmlReadability(currentFlags.Message); cleanErr != nil {
|
||||
@@ -101,11 +122,11 @@ func processYoutubeVideo(
|
||||
}
|
||||
}
|
||||
if flags.YouTubeTranscriptWithTimestamps {
|
||||
if transcript, err = registry.YouTube.GrabTranscriptWithTimestamps(videoId, language); err != nil {
|
||||
if transcript, err = registry.YouTube.GrabTranscriptWithTimestampsWithArgs(videoId, language, flags.YtDlpArgs); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if transcript, err = registry.YouTube.GrabTranscript(videoId, language); err != nil {
|
||||
if transcript, err = registry.YouTube.GrabTranscriptWithArgs(videoId, language, flags.YtDlpArgs); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -142,3 +163,21 @@ func WriteOutput(message string, outputFile string) (err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// configureOpenAIResponsesAPI configures the OpenAI client's Responses API setting based on the CLI flag
|
||||
func configureOpenAIResponsesAPI(registry *core.PluginRegistry, disableResponsesAPI bool) {
|
||||
// Find the OpenAI vendor in the registry
|
||||
if registry != nil && registry.VendorsAll != nil {
|
||||
for _, vendor := range registry.VendorsAll.Vendors {
|
||||
if vendor.GetName() == "OpenAI" {
|
||||
// Type assertion to access the OpenAI-specific method
|
||||
if openaiClient, ok := vendor.(*openai.Client); ok {
|
||||
// Invert the disable flag to get the enable flag
|
||||
enableResponsesAPI := !disableResponsesAPI
|
||||
openaiClient.SetResponsesAPIEnabled(enableResponsesAPI)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,3 +24,7 @@ raw: false
|
||||
suppressThink: false
|
||||
thinkStartTag: "<think>"
|
||||
thinkEndTag: "</think>"
|
||||
|
||||
# OpenAI Responses API settings
|
||||
# (use this for llama-server or other OpenAI-compatible local servers)
|
||||
disableResponsesAPI: true
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/chat"
|
||||
"github.com/danielmiessler/fabric/internal/domain"
|
||||
debuglog "github.com/danielmiessler/fabric/internal/log"
|
||||
"github.com/danielmiessler/fabric/internal/util"
|
||||
"github.com/jessevdk/go-flags"
|
||||
"golang.org/x/text/language"
|
||||
@@ -20,84 +21,93 @@ import (
|
||||
)
|
||||
|
||||
// Flags create flags struct. the users flags go into this, this will be passed to the chat struct in cli
|
||||
// Chat parameter defaults set in the struct tags must match domain.Default* constants
|
||||
|
||||
type Flags struct {
|
||||
Pattern string `short:"p" long:"pattern" yaml:"pattern" description:"Choose a pattern from the available patterns" default:""`
|
||||
PatternVariables map[string]string `short:"v" long:"variable" description:"Values for pattern variables, e.g. -v=#role:expert -v=#points:30"`
|
||||
Context string `short:"C" long:"context" description:"Choose a context from the available contexts" default:""`
|
||||
Session string `long:"session" description:"Choose a session from the available sessions"`
|
||||
Attachments []string `short:"a" long:"attachment" description:"Attachment path or URL (e.g. for OpenAI image recognition messages)"`
|
||||
Setup bool `short:"S" long:"setup" description:"Run setup for all reconfigurable parts of fabric"`
|
||||
Temperature float64 `short:"t" long:"temperature" yaml:"temperature" description:"Set temperature" default:"0.7"`
|
||||
TopP float64 `short:"T" long:"topp" yaml:"topp" description:"Set top P" default:"0.9"`
|
||||
Stream bool `short:"s" long:"stream" yaml:"stream" description:"Stream"`
|
||||
PresencePenalty float64 `short:"P" long:"presencepenalty" yaml:"presencepenalty" description:"Set presence penalty" default:"0.0"`
|
||||
Raw bool `short:"r" long:"raw" yaml:"raw" description:"Use the defaults of the model without sending chat options (like temperature etc.) and use the user role instead of the system role for patterns."`
|
||||
FrequencyPenalty float64 `short:"F" long:"frequencypenalty" yaml:"frequencypenalty" description:"Set frequency penalty" default:"0.0"`
|
||||
ListPatterns bool `short:"l" long:"listpatterns" description:"List all patterns"`
|
||||
ListAllModels bool `short:"L" long:"listmodels" description:"List all available models"`
|
||||
ListAllContexts bool `short:"x" long:"listcontexts" description:"List all contexts"`
|
||||
ListAllSessions bool `short:"X" long:"listsessions" description:"List all sessions"`
|
||||
UpdatePatterns bool `short:"U" long:"updatepatterns" description:"Update patterns"`
|
||||
Message string `hidden:"true" description:"Messages to send to chat"`
|
||||
Copy bool `short:"c" long:"copy" description:"Copy to clipboard"`
|
||||
Model string `short:"m" long:"model" yaml:"model" description:"Choose model"`
|
||||
ModelContextLength int `long:"modelContextLength" yaml:"modelContextLength" description:"Model context length (only affects ollama)"`
|
||||
Output string `short:"o" long:"output" description:"Output to file" default:""`
|
||||
OutputSession bool `long:"output-session" description:"Output the entire session (also a temporary one) to the output file"`
|
||||
LatestPatterns string `short:"n" long:"latest" description:"Number of latest patterns to list" default:"0"`
|
||||
ChangeDefaultModel bool `short:"d" long:"changeDefaultModel" description:"Change default model"`
|
||||
YouTube string `short:"y" long:"youtube" description:"YouTube video or play list \"URL\" to grab transcript, comments from it and send to chat or print it put to the console and store it in the output file"`
|
||||
YouTubePlaylist bool `long:"playlist" description:"Prefer playlist over video if both ids are present in the URL"`
|
||||
YouTubeTranscript bool `long:"transcript" description:"Grab transcript from YouTube video and send to chat (it is used per default)."`
|
||||
YouTubeTranscriptWithTimestamps bool `long:"transcript-with-timestamps" description:"Grab transcript from YouTube video with timestamps and send to chat"`
|
||||
YouTubeComments bool `long:"comments" description:"Grab comments from YouTube video and send to chat"`
|
||||
YouTubeMetadata bool `long:"metadata" description:"Output video metadata"`
|
||||
Language string `short:"g" long:"language" description:"Specify the Language Code for the chat, e.g. -g=en -g=zh" default:""`
|
||||
ScrapeURL string `short:"u" long:"scrape_url" description:"Scrape website URL to markdown using Jina AI"`
|
||||
ScrapeQuestion string `short:"q" long:"scrape_question" description:"Search question using Jina AI"`
|
||||
Seed int `short:"e" long:"seed" yaml:"seed" description:"Seed to be used for LMM generation"`
|
||||
WipeContext string `short:"w" long:"wipecontext" description:"Wipe context"`
|
||||
WipeSession string `short:"W" long:"wipesession" description:"Wipe session"`
|
||||
PrintContext string `long:"printcontext" description:"Print context"`
|
||||
PrintSession string `long:"printsession" description:"Print session"`
|
||||
HtmlReadability bool `long:"readability" description:"Convert HTML input into a clean, readable view"`
|
||||
InputHasVars bool `long:"input-has-vars" description:"Apply variables to user input"`
|
||||
DryRun bool `long:"dry-run" description:"Show what would be sent to the model without actually sending it"`
|
||||
Serve bool `long:"serve" description:"Serve the Fabric Rest API"`
|
||||
ServeOllama bool `long:"serveOllama" description:"Serve the Fabric Rest API with ollama endpoints"`
|
||||
ServeAddress string `long:"address" description:"The address to bind the REST API" default:":8080"`
|
||||
ServeAPIKey string `long:"api-key" description:"API key used to secure server routes" default:""`
|
||||
Config string `long:"config" description:"Path to YAML config file"`
|
||||
Version bool `long:"version" description:"Print current version"`
|
||||
ListExtensions bool `long:"listextensions" description:"List all registered extensions"`
|
||||
AddExtension string `long:"addextension" description:"Register a new extension from config file path"`
|
||||
RemoveExtension string `long:"rmextension" description:"Remove a registered extension by name"`
|
||||
Strategy string `long:"strategy" description:"Choose a strategy from the available strategies" default:""`
|
||||
ListStrategies bool `long:"liststrategies" description:"List all strategies"`
|
||||
ListVendors bool `long:"listvendors" description:"List all vendors"`
|
||||
ShellCompleteOutput bool `long:"shell-complete-list" description:"Output raw list without headers/formatting (for shell completion)"`
|
||||
Search bool `long:"search" description:"Enable web search tool for supported models (Anthropic, OpenAI)"`
|
||||
SearchLocation string `long:"search-location" description:"Set location for web search results (e.g., 'America/Los_Angeles')"`
|
||||
ImageFile string `long:"image-file" description:"Save generated image to specified file path (e.g., 'output.png')"`
|
||||
ImageSize string `long:"image-size" description:"Image dimensions: 1024x1024, 1536x1024, 1024x1536, auto (default: auto)"`
|
||||
ImageQuality string `long:"image-quality" description:"Image quality: low, medium, high, auto (default: auto)"`
|
||||
ImageCompression int `long:"image-compression" description:"Compression level 0-100 for JPEG/WebP formats (default: not set)"`
|
||||
ImageBackground string `long:"image-background" description:"Background type: opaque, transparent (default: opaque, only for PNG/WebP)"`
|
||||
SuppressThink bool `long:"suppress-think" yaml:"suppressThink" description:"Suppress text enclosed in thinking tags"`
|
||||
ThinkStartTag string `long:"think-start-tag" yaml:"thinkStartTag" description:"Start tag for thinking sections" default:"<think>"`
|
||||
ThinkEndTag string `long:"think-end-tag" yaml:"thinkEndTag" description:"End tag for thinking sections" default:"</think>"`
|
||||
}
|
||||
|
||||
var debug = false
|
||||
|
||||
func Debugf(format string, a ...interface{}) {
|
||||
if debug {
|
||||
fmt.Printf("DEBUG: "+format, a...)
|
||||
}
|
||||
Pattern string `short:"p" long:"pattern" yaml:"pattern" description:"Choose a pattern from the available patterns" default:""`
|
||||
PatternVariables map[string]string `short:"v" long:"variable" description:"Values for pattern variables, e.g. -v=#role:expert -v=#points:30"`
|
||||
Context string `short:"C" long:"context" description:"Choose a context from the available contexts" default:""`
|
||||
Session string `long:"session" description:"Choose a session from the available sessions"`
|
||||
Attachments []string `short:"a" long:"attachment" description:"Attachment path or URL (e.g. for OpenAI image recognition messages)"`
|
||||
Setup bool `short:"S" long:"setup" description:"Run setup for all reconfigurable parts of fabric"`
|
||||
Temperature float64 `short:"t" long:"temperature" yaml:"temperature" description:"Set temperature" default:"0.7"`
|
||||
TopP float64 `short:"T" long:"topp" yaml:"topp" description:"Set top P" default:"0.9"`
|
||||
Stream bool `short:"s" long:"stream" yaml:"stream" description:"Stream"`
|
||||
PresencePenalty float64 `short:"P" long:"presencepenalty" yaml:"presencepenalty" description:"Set presence penalty" default:"0.0"`
|
||||
Raw bool `short:"r" long:"raw" yaml:"raw" description:"Use the defaults of the model without sending chat options (like temperature etc.) and use the user role instead of the system role for patterns."`
|
||||
FrequencyPenalty float64 `short:"F" long:"frequencypenalty" yaml:"frequencypenalty" description:"Set frequency penalty" default:"0.0"`
|
||||
ListPatterns bool `short:"l" long:"listpatterns" description:"List all patterns"`
|
||||
ListAllModels bool `short:"L" long:"listmodels" description:"List all available models"`
|
||||
ListAllContexts bool `short:"x" long:"listcontexts" description:"List all contexts"`
|
||||
ListAllSessions bool `short:"X" long:"listsessions" description:"List all sessions"`
|
||||
UpdatePatterns bool `short:"U" long:"updatepatterns" description:"Update patterns"`
|
||||
Message string `hidden:"true" description:"Messages to send to chat"`
|
||||
Copy bool `short:"c" long:"copy" description:"Copy to clipboard"`
|
||||
Model string `short:"m" long:"model" yaml:"model" description:"Choose model"`
|
||||
Vendor string `short:"V" long:"vendor" yaml:"vendor" description:"Specify vendor for the selected model (e.g., -V \"LM Studio\" -m openai/gpt-oss-20b)"`
|
||||
ModelContextLength int `long:"modelContextLength" yaml:"modelContextLength" description:"Model context length (only affects ollama)"`
|
||||
Output string `short:"o" long:"output" description:"Output to file" default:""`
|
||||
OutputSession bool `long:"output-session" description:"Output the entire session (also a temporary one) to the output file"`
|
||||
LatestPatterns string `short:"n" long:"latest" description:"Number of latest patterns to list" default:"0"`
|
||||
ChangeDefaultModel bool `short:"d" long:"changeDefaultModel" description:"Change default model"`
|
||||
YouTube string `short:"y" long:"youtube" description:"YouTube video or play list \"URL\" to grab transcript, comments from it and send to chat or print it put to the console and store it in the output file"`
|
||||
YouTubePlaylist bool `long:"playlist" description:"Prefer playlist over video if both ids are present in the URL"`
|
||||
YouTubeTranscript bool `long:"transcript" description:"Grab transcript from YouTube video and send to chat (it is used per default)."`
|
||||
YouTubeTranscriptWithTimestamps bool `long:"transcript-with-timestamps" description:"Grab transcript from YouTube video with timestamps and send to chat"`
|
||||
YouTubeComments bool `long:"comments" description:"Grab comments from YouTube video and send to chat"`
|
||||
YouTubeMetadata bool `long:"metadata" description:"Output video metadata"`
|
||||
YtDlpArgs string `long:"yt-dlp-args" yaml:"ytDlpArgs" description:"Additional arguments to pass to yt-dlp (e.g. '--cookies-from-browser brave')"`
|
||||
Language string `short:"g" long:"language" description:"Specify the Language Code for the chat, e.g. -g=en -g=zh" default:""`
|
||||
ScrapeURL string `short:"u" long:"scrape_url" description:"Scrape website URL to markdown using Jina AI"`
|
||||
ScrapeQuestion string `short:"q" long:"scrape_question" description:"Search question using Jina AI"`
|
||||
Seed int `short:"e" long:"seed" yaml:"seed" description:"Seed to be used for LMM generation"`
|
||||
WipeContext string `short:"w" long:"wipecontext" description:"Wipe context"`
|
||||
WipeSession string `short:"W" long:"wipesession" description:"Wipe session"`
|
||||
PrintContext string `long:"printcontext" description:"Print context"`
|
||||
PrintSession string `long:"printsession" description:"Print session"`
|
||||
HtmlReadability bool `long:"readability" description:"Convert HTML input into a clean, readable view"`
|
||||
InputHasVars bool `long:"input-has-vars" description:"Apply variables to user input"`
|
||||
NoVariableReplacement bool `long:"no-variable-replacement" description:"Disable pattern variable replacement"`
|
||||
DryRun bool `long:"dry-run" description:"Show what would be sent to the model without actually sending it"`
|
||||
Serve bool `long:"serve" description:"Serve the Fabric Rest API"`
|
||||
ServeOllama bool `long:"serveOllama" description:"Serve the Fabric Rest API with ollama endpoints"`
|
||||
ServeAddress string `long:"address" description:"The address to bind the REST API" default:":8080"`
|
||||
ServeAPIKey string `long:"api-key" description:"API key used to secure server routes" default:""`
|
||||
Config string `long:"config" description:"Path to YAML config file"`
|
||||
Version bool `long:"version" description:"Print current version"`
|
||||
ListExtensions bool `long:"listextensions" description:"List all registered extensions"`
|
||||
AddExtension string `long:"addextension" description:"Register a new extension from config file path"`
|
||||
RemoveExtension string `long:"rmextension" description:"Remove a registered extension by name"`
|
||||
Strategy string `long:"strategy" description:"Choose a strategy from the available strategies" default:""`
|
||||
ListStrategies bool `long:"liststrategies" description:"List all strategies"`
|
||||
ListVendors bool `long:"listvendors" description:"List all vendors"`
|
||||
ShellCompleteOutput bool `long:"shell-complete-list" description:"Output raw list without headers/formatting (for shell completion)"`
|
||||
Search bool `long:"search" description:"Enable web search tool for supported models (Anthropic, OpenAI, Gemini)"`
|
||||
SearchLocation string `long:"search-location" description:"Set location for web search results (e.g., 'America/Los_Angeles')"`
|
||||
ImageFile string `long:"image-file" description:"Save generated image to specified file path (e.g., 'output.png')"`
|
||||
ImageSize string `long:"image-size" description:"Image dimensions: 1024x1024, 1536x1024, 1024x1536, auto (default: auto)"`
|
||||
ImageQuality string `long:"image-quality" description:"Image quality: low, medium, high, auto (default: auto)"`
|
||||
ImageCompression int `long:"image-compression" description:"Compression level 0-100 for JPEG/WebP formats (default: not set)"`
|
||||
ImageBackground string `long:"image-background" description:"Background type: opaque, transparent (default: opaque, only for PNG/WebP)"`
|
||||
SuppressThink bool `long:"suppress-think" yaml:"suppressThink" description:"Suppress text enclosed in thinking tags"`
|
||||
ThinkStartTag string `long:"think-start-tag" yaml:"thinkStartTag" description:"Start tag for thinking sections" default:"<think>"`
|
||||
ThinkEndTag string `long:"think-end-tag" yaml:"thinkEndTag" description:"End tag for thinking sections" default:"</think>"`
|
||||
DisableResponsesAPI bool `long:"disable-responses-api" yaml:"disableResponsesAPI" description:"Disable OpenAI Responses API (default: false)"`
|
||||
TranscribeFile string `long:"transcribe-file" yaml:"transcribeFile" description:"Audio or video file to transcribe"`
|
||||
TranscribeModel string `long:"transcribe-model" yaml:"transcribeModel" description:"Model to use for transcription (separate from chat model)"`
|
||||
SplitMediaFile bool `long:"split-media-file" yaml:"splitMediaFile" description:"Split audio/video files larger than 25MB using ffmpeg"`
|
||||
Voice string `long:"voice" yaml:"voice" description:"TTS voice name for supported models (e.g., Kore, Charon, Puck)" default:"Kore"`
|
||||
ListGeminiVoices bool `long:"list-gemini-voices" description:"List all available Gemini TTS voices"`
|
||||
ListTranscriptionModels bool `long:"list-transcription-models" description:"List all available transcription models"`
|
||||
Notification bool `long:"notification" yaml:"notification" description:"Send desktop notification when command completes"`
|
||||
NotificationCommand string `long:"notification-command" yaml:"notificationCommand" description:"Custom command to run for notifications (overrides built-in notifications)"`
|
||||
Thinking domain.ThinkingLevel `long:"thinking" yaml:"thinking" description:"Set reasoning/thinking level (e.g., off, low, medium, high, or numeric tokens for Anthropic or Google Gemini)"`
|
||||
Debug int `long:"debug" description:"Set debug level (0=off, 1=basic, 2=detailed, 3=trace)" default:"0"`
|
||||
}
|
||||
|
||||
// Init Initialize flags. returns a Flags struct and an error
|
||||
func Init() (ret *Flags, err error) {
|
||||
debuglog.SetLevel(debuglog.LevelFromInt(parseDebugLevel(os.Args[1:])))
|
||||
// Track which yaml-configured flags were set on CLI
|
||||
usedFlags := make(map[string]bool)
|
||||
yamlArgsScan := os.Args[1:]
|
||||
@@ -113,11 +123,11 @@ func Init() (ret *Flags, err error) {
|
||||
shortTag := field.Tag.Get("short")
|
||||
if longTag != "" {
|
||||
flagToYamlTag[longTag] = yamlTag
|
||||
Debugf("Mapped long flag %s to yaml tag %s\n", longTag, yamlTag)
|
||||
debuglog.Debug(debuglog.Detailed, "Mapped long flag %s to yaml tag %s\n", longTag, yamlTag)
|
||||
}
|
||||
if shortTag != "" {
|
||||
flagToYamlTag[shortTag] = yamlTag
|
||||
Debugf("Mapped short flag %s to yaml tag %s\n", shortTag, yamlTag)
|
||||
debuglog.Debug(debuglog.Detailed, "Mapped short flag %s to yaml tag %s\n", shortTag, yamlTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,7 +139,7 @@ func Init() (ret *Flags, err error) {
|
||||
if flag != "" {
|
||||
if yamlTag, exists := flagToYamlTag[flag]; exists {
|
||||
usedFlags[yamlTag] = true
|
||||
Debugf("CLI flag used: %s (yaml: %s)\n", flag, yamlTag)
|
||||
debuglog.Debug(debuglog.Detailed, "CLI flag used: %s (yaml: %s)\n", flag, yamlTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,14 +151,15 @@ func Init() (ret *Flags, err error) {
|
||||
if args, err = parser.Parse(); err != nil {
|
||||
return
|
||||
}
|
||||
debuglog.SetLevel(debuglog.LevelFromInt(ret.Debug))
|
||||
|
||||
// Check to see if a ~/.fabric.yaml config file exists (only when user didn't specify a config)
|
||||
// Check to see if a ~/.config/fabric/config.yaml config file exists (only when user didn't specify a config)
|
||||
if ret.Config == "" {
|
||||
// Default to ~/.fabric.yaml if no config specified
|
||||
// Default to ~/.config/fabric/config.yaml if no config specified
|
||||
if defaultConfigPath, err := util.GetDefaultConfigPath(); err == nil && defaultConfigPath != "" {
|
||||
ret.Config = defaultConfigPath
|
||||
} else if err != nil {
|
||||
Debugf("Could not determine default config path: %v\n", err)
|
||||
debuglog.Debug(debuglog.Detailed, "Could not determine default config path: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,13 +184,13 @@ func Init() (ret *Flags, err error) {
|
||||
if flagField.CanSet() {
|
||||
if yamlField.Type() != flagField.Type() {
|
||||
if err := assignWithConversion(flagField, yamlField); err != nil {
|
||||
Debugf("Type conversion failed for %s: %v\n", yamlTag, err)
|
||||
debuglog.Debug(debuglog.Detailed, "Type conversion failed for %s: %v\n", yamlTag, err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
flagField.Set(yamlField)
|
||||
}
|
||||
Debugf("Applied YAML value for %s: %v\n", yamlTag, yamlField.Interface())
|
||||
debuglog.Debug(debuglog.Detailed, "Applied YAML value for %s: %v\n", yamlTag, yamlField.Interface())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,6 +216,22 @@ func Init() (ret *Flags, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func parseDebugLevel(args []string) int {
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
if arg == "--debug" && i+1 < len(args) {
|
||||
if lvl, err := strconv.Atoi(args[i+1]); err == nil {
|
||||
return lvl
|
||||
}
|
||||
} else if strings.HasPrefix(arg, "--debug=") {
|
||||
if lvl, err := strconv.Atoi(strings.TrimPrefix(arg, "--debug=")); err == nil {
|
||||
return lvl
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func extractFlag(arg string) string {
|
||||
var flag string
|
||||
if strings.HasPrefix(arg, "--") {
|
||||
@@ -274,7 +301,7 @@ func loadYAMLConfig(configPath string) (*Flags, error) {
|
||||
return nil, fmt.Errorf("error parsing config file: %w", err)
|
||||
}
|
||||
|
||||
Debugf("Config: %v\n", config)
|
||||
debuglog.Debug(debuglog.Detailed, "Config: %v\n", config)
|
||||
|
||||
return config, nil
|
||||
}
|
||||
@@ -422,37 +449,42 @@ func (o *Flags) BuildChatOptions() (ret *domain.ChatOptions, err error) {
|
||||
}
|
||||
|
||||
ret = &domain.ChatOptions{
|
||||
Model: o.Model,
|
||||
Temperature: o.Temperature,
|
||||
TopP: o.TopP,
|
||||
PresencePenalty: o.PresencePenalty,
|
||||
FrequencyPenalty: o.FrequencyPenalty,
|
||||
Raw: o.Raw,
|
||||
Seed: o.Seed,
|
||||
ModelContextLength: o.ModelContextLength,
|
||||
Search: o.Search,
|
||||
SearchLocation: o.SearchLocation,
|
||||
ImageFile: o.ImageFile,
|
||||
ImageSize: o.ImageSize,
|
||||
ImageQuality: o.ImageQuality,
|
||||
ImageCompression: o.ImageCompression,
|
||||
ImageBackground: o.ImageBackground,
|
||||
SuppressThink: o.SuppressThink,
|
||||
ThinkStartTag: startTag,
|
||||
ThinkEndTag: endTag,
|
||||
Model: o.Model,
|
||||
Temperature: o.Temperature,
|
||||
TopP: o.TopP,
|
||||
PresencePenalty: o.PresencePenalty,
|
||||
FrequencyPenalty: o.FrequencyPenalty,
|
||||
Raw: o.Raw,
|
||||
Seed: o.Seed,
|
||||
Thinking: o.Thinking,
|
||||
ModelContextLength: o.ModelContextLength,
|
||||
Search: o.Search,
|
||||
SearchLocation: o.SearchLocation,
|
||||
ImageFile: o.ImageFile,
|
||||
ImageSize: o.ImageSize,
|
||||
ImageQuality: o.ImageQuality,
|
||||
ImageCompression: o.ImageCompression,
|
||||
ImageBackground: o.ImageBackground,
|
||||
SuppressThink: o.SuppressThink,
|
||||
ThinkStartTag: startTag,
|
||||
ThinkEndTag: endTag,
|
||||
Voice: o.Voice,
|
||||
Notification: o.Notification || o.NotificationCommand != "",
|
||||
NotificationCommand: o.NotificationCommand,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Flags) BuildChatRequest(Meta string) (ret *domain.ChatRequest, err error) {
|
||||
ret = &domain.ChatRequest{
|
||||
ContextName: o.Context,
|
||||
SessionName: o.Session,
|
||||
PatternName: o.Pattern,
|
||||
StrategyName: o.Strategy,
|
||||
PatternVariables: o.PatternVariables,
|
||||
InputHasVars: o.InputHasVars,
|
||||
Meta: Meta,
|
||||
ContextName: o.Context,
|
||||
SessionName: o.Session,
|
||||
PatternName: o.Pattern,
|
||||
StrategyName: o.Strategy,
|
||||
PatternVariables: o.PatternVariables,
|
||||
InputHasVars: o.InputHasVars,
|
||||
NoVariableReplacement: o.NoVariableReplacement,
|
||||
Meta: Meta,
|
||||
}
|
||||
|
||||
var message *chat.ChatCompletionMessage
|
||||
|
||||
@@ -64,6 +64,7 @@ func TestBuildChatOptions(t *testing.T) {
|
||||
FrequencyPenalty: 0.2,
|
||||
Raw: false,
|
||||
Seed: 1,
|
||||
Thinking: domain.ThinkingLevel(""),
|
||||
SuppressThink: false,
|
||||
ThinkStartTag: "<think>",
|
||||
ThinkEndTag: "</think>",
|
||||
@@ -88,6 +89,7 @@ func TestBuildChatOptionsDefaultSeed(t *testing.T) {
|
||||
FrequencyPenalty: 0.2,
|
||||
Raw: false,
|
||||
Seed: 0,
|
||||
Thinking: domain.ThinkingLevel(""),
|
||||
SuppressThink: false,
|
||||
ThinkStartTag: "<think>",
|
||||
ThinkEndTag: "</think>",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@@ -8,6 +9,9 @@ import (
|
||||
"github.com/danielmiessler/fabric/internal/plugins/db/fsdb"
|
||||
)
|
||||
|
||||
const ConfigDirPerms os.FileMode = 0755
|
||||
const EnvFilePerms os.FileMode = 0644
|
||||
|
||||
// initializeFabric initializes the fabric database and plugin registry
|
||||
func initializeFabric() (registry *core.PluginRegistry, err error) {
|
||||
var homedir string
|
||||
@@ -26,3 +30,27 @@ func initializeFabric() (registry *core.PluginRegistry, err error) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ensureEnvFile checks for the default ~/.config/fabric/.env file and creates it
|
||||
// along with the parent directory if it does not exist.
|
||||
func ensureEnvFile() (err error) {
|
||||
var homedir string
|
||||
if homedir, err = os.UserHomeDir(); err != nil {
|
||||
return fmt.Errorf("could not determine user home directory: %w", err)
|
||||
}
|
||||
configDir := filepath.Join(homedir, ".config", "fabric")
|
||||
envPath := filepath.Join(configDir, ".env")
|
||||
|
||||
if _, statErr := os.Stat(envPath); statErr != nil {
|
||||
if !os.IsNotExist(statErr) {
|
||||
return fmt.Errorf("could not stat .env file: %w", statErr)
|
||||
}
|
||||
if err = os.MkdirAll(configDir, ConfigDirPerms); err != nil {
|
||||
return fmt.Errorf("could not create config directory: %w", err)
|
||||
}
|
||||
if err = os.WriteFile(envPath, []byte{}, EnvFilePerms); err != nil {
|
||||
return fmt.Errorf("could not create .env file: %w", err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
openai "github.com/openai/openai-go"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/core"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/gemini"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/db/fsdb"
|
||||
)
|
||||
|
||||
@@ -34,7 +38,11 @@ func handleListingCommands(currentFlags *Flags, fabricDb *fsdb.Db, registry *cor
|
||||
if models, err = registry.VendorManager.GetModels(); err != nil {
|
||||
return true, err
|
||||
}
|
||||
models.Print(currentFlags.ShellCompleteOutput)
|
||||
if currentFlags.ShellCompleteOutput {
|
||||
models.Print(true)
|
||||
} else {
|
||||
models.PrintWithVendor(false, registry.Defaults.Vendor.Value, registry.Defaults.Model.Value)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -58,5 +66,36 @@ func handleListingCommands(currentFlags *Flags, fabricDb *fsdb.Db, registry *cor
|
||||
return true, err
|
||||
}
|
||||
|
||||
if currentFlags.ListGeminiVoices {
|
||||
voicesList := gemini.ListGeminiVoices(currentFlags.ShellCompleteOutput)
|
||||
fmt.Print(voicesList)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if currentFlags.ListTranscriptionModels {
|
||||
listTranscriptionModels(currentFlags.ShellCompleteOutput)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// listTranscriptionModels lists all available transcription models
|
||||
func listTranscriptionModels(shellComplete bool) {
|
||||
models := []string{
|
||||
string(openai.AudioModelWhisper1),
|
||||
string(openai.AudioModelGPT4oMiniTranscribe),
|
||||
string(openai.AudioModelGPT4oTranscribe),
|
||||
}
|
||||
|
||||
if shellComplete {
|
||||
for _, model := range models {
|
||||
fmt.Println(model)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Available transcription models:")
|
||||
for _, model := range models {
|
||||
fmt.Printf(" %s\n", model)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@ package cli
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/atotto/clipboard"
|
||||
debuglog "github.com/danielmiessler/fabric/internal/log"
|
||||
)
|
||||
|
||||
func CopyToClipboard(message string) (err error) {
|
||||
@@ -15,6 +18,10 @@ func CopyToClipboard(message string) (err error) {
|
||||
}
|
||||
|
||||
func CreateOutputFile(message string, fileName string) (err error) {
|
||||
if _, err = os.Stat(fileName); err == nil {
|
||||
err = fmt.Errorf("file %s already exists, not overwriting. Rename the existing file or choose a different name", fileName)
|
||||
return
|
||||
}
|
||||
var file *os.File
|
||||
if file, err = os.Create(fileName); err != nil {
|
||||
err = fmt.Errorf("error creating file: %v", err)
|
||||
@@ -24,7 +31,41 @@ func CreateOutputFile(message string, fileName string) (err error) {
|
||||
if _, err = file.WriteString(message); err != nil {
|
||||
err = fmt.Errorf("error writing to file: %v", err)
|
||||
} else {
|
||||
fmt.Printf("\n\n... written to %s\n", fileName)
|
||||
debuglog.Log("\n\n[Output also written to %s]\n", fileName)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CreateAudioOutputFile creates a binary file for audio data
|
||||
func CreateAudioOutputFile(audioData []byte, fileName string) (err error) {
|
||||
// If no extension is provided, default to .wav
|
||||
if filepath.Ext(fileName) == "" {
|
||||
fileName += ".wav"
|
||||
}
|
||||
|
||||
// File existence check is now done in the CLI layer before TTS generation
|
||||
var file *os.File
|
||||
if file, err = os.Create(fileName); err != nil {
|
||||
err = fmt.Errorf("error creating audio file: %v", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err = file.Write(audioData); err != nil {
|
||||
err = fmt.Errorf("error writing audio data to file: %v", err)
|
||||
}
|
||||
// No redundant output message here - the CLI layer handles success messaging
|
||||
return
|
||||
}
|
||||
|
||||
// IsAudioFormat checks if the filename suggests an audio format
|
||||
func IsAudioFormat(fileName string) bool {
|
||||
ext := strings.ToLower(filepath.Ext(fileName))
|
||||
audioExts := []string{".wav", ".mp3", ".m4a", ".aac", ".ogg", ".flac"}
|
||||
for _, audioExt := range audioExts {
|
||||
if ext == audioExt {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
35
internal/cli/transcribe.go
Normal file
35
internal/cli/transcribe.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/core"
|
||||
)
|
||||
|
||||
type transcriber interface {
|
||||
TranscribeFile(ctx context.Context, filePath, model string, split bool) (string, error)
|
||||
}
|
||||
|
||||
func handleTranscription(flags *Flags, registry *core.PluginRegistry) (message string, err error) {
|
||||
vendorName := flags.Vendor
|
||||
if vendorName == "" {
|
||||
vendorName = "OpenAI"
|
||||
}
|
||||
vendor, ok := registry.VendorManager.VendorsByName[vendorName]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("vendor %s not configured", vendorName)
|
||||
}
|
||||
tr, ok := vendor.(transcriber)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("vendor %s does not support audio transcription", vendorName)
|
||||
}
|
||||
model := flags.TranscribeModel
|
||||
if model == "" {
|
||||
return "", fmt.Errorf("transcription model is required (use --transcribe-model)")
|
||||
}
|
||||
if message, err = tr.TranscribeFile(context.Background(), flags.TranscribeFile, model, flags.SplitMediaFile); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -180,7 +180,7 @@ func (o *Chatter) BuildSession(request *domain.ChatRequest, raw bool) (session *
|
||||
}
|
||||
|
||||
// Now we know request.Message is not nil, process template variables
|
||||
if request.InputHasVars {
|
||||
if request.InputHasVars && !request.NoVariableReplacement {
|
||||
request.Message.Content, err = template.ApplyTemplate(request.Message.Content, request.PatternVariables, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -190,7 +190,12 @@ func (o *Chatter) BuildSession(request *domain.ChatRequest, raw bool) (session *
|
||||
var patternContent string
|
||||
inputUsed := false
|
||||
if request.PatternName != "" {
|
||||
pattern, err := o.db.Patterns.GetApplyVariables(request.PatternName, request.PatternVariables, request.Message.Content)
|
||||
var pattern *fsdb.Pattern
|
||||
if request.NoVariableReplacement {
|
||||
pattern, err = o.db.Patterns.GetWithoutVariables(request.PatternName, request.Message.Content)
|
||||
} else {
|
||||
pattern, err = o.db.Patterns.GetApplyVariables(request.PatternName, request.PatternVariables, request.Message.Content)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get pattern %s: %v", request.PatternName, err)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
debuglog "github.com/danielmiessler/fabric/internal/log"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/anthropic"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/azure"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/bedrock"
|
||||
@@ -20,7 +21,7 @@ import (
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/ollama"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/openai"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/openai_compatible"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/perplexity" // Added Perplexity plugin
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/perplexity"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/strategy"
|
||||
|
||||
"github.com/samber/lo"
|
||||
@@ -37,12 +38,16 @@ import (
|
||||
"github.com/danielmiessler/fabric/internal/util"
|
||||
)
|
||||
|
||||
// hasAWSCredentials checks if any AWS credentials are present either in the
|
||||
// environment variables or in the default/shared credentials file. It doesn't
|
||||
// attempt to verify the validity of the credentials, but simply ensures that a
|
||||
// potential authentication source exists so we can safely initialize the
|
||||
// Bedrock client without causing the AWS SDK to search for credentials.
|
||||
// hasAWSCredentials checks if Bedrock is properly configured by ensuring both
|
||||
// AWS credentials and BEDROCK_AWS_REGION are present. This prevents the Bedrock
|
||||
// client from being initialized when AWS credentials exist for other purposes.
|
||||
func hasAWSCredentials() bool {
|
||||
// First check if BEDROCK_AWS_REGION is set - this is required for Bedrock
|
||||
if os.Getenv("BEDROCK_AWS_REGION") == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Then check if AWS credentials are available
|
||||
if os.Getenv("AWS_PROFILE") != "" ||
|
||||
os.Getenv("AWS_ROLE_SESSION_NAME") != "" ||
|
||||
(os.Getenv("AWS_ACCESS_KEY_ID") != "" && os.Getenv("AWS_SECRET_ACCESS_KEY") != "") {
|
||||
@@ -284,7 +289,7 @@ func (o *PluginRegistry) Configure() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (o *PluginRegistry) GetChatter(model string, modelContextLength int, strategy string, stream bool, dryRun bool) (ret *Chatter, err error) {
|
||||
func (o *PluginRegistry) GetChatter(model string, modelContextLength int, vendorName string, strategy string, stream bool, dryRun bool) (ret *Chatter, err error) {
|
||||
ret = &Chatter{
|
||||
db: o.Db,
|
||||
Stream: stream,
|
||||
@@ -313,14 +318,32 @@ func (o *PluginRegistry) GetChatter(model string, modelContextLength int, strate
|
||||
ret.model = defaultModel
|
||||
}
|
||||
} else if model == "" {
|
||||
ret.vendor = vendorManager.FindByName(defaultVendor)
|
||||
if vendorName != "" {
|
||||
ret.vendor = vendorManager.FindByName(vendorName)
|
||||
} else {
|
||||
ret.vendor = vendorManager.FindByName(defaultVendor)
|
||||
}
|
||||
ret.model = defaultModel
|
||||
} else {
|
||||
var models *ai.VendorsModels
|
||||
if models, err = vendorManager.GetModels(); err != nil {
|
||||
return
|
||||
}
|
||||
ret.vendor = vendorManager.FindByName(models.FindGroupsByItemFirst(model))
|
||||
if vendorName != "" {
|
||||
// ensure vendor exists and provides model
|
||||
ret.vendor = vendorManager.FindByName(vendorName)
|
||||
availableVendors := models.FindGroupsByItem(model)
|
||||
if ret.vendor == nil || !lo.Contains(availableVendors, vendorName) {
|
||||
err = fmt.Errorf("model %s not available for vendor %s", model, vendorName)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
availableVendors := models.FindGroupsByItem(model)
|
||||
if len(availableVendors) > 1 {
|
||||
debuglog.Log("Warning: multiple vendors provide model %s: %s. Using %s. Specify --vendor to select a vendor.\n", model, strings.Join(availableVendors, ", "), availableVendors[0])
|
||||
}
|
||||
ret.vendor = vendorManager.FindByName(models.FindGroupsByItemFirst(model))
|
||||
}
|
||||
ret.model = model
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/chat"
|
||||
"github.com/danielmiessler/fabric/internal/domain"
|
||||
debuglog "github.com/danielmiessler/fabric/internal/log"
|
||||
"github.com/danielmiessler/fabric/internal/plugins"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/db/fsdb"
|
||||
"github.com/danielmiessler/fabric/internal/tools"
|
||||
)
|
||||
|
||||
func TestSaveEnvFile(t *testing.T) {
|
||||
@@ -19,3 +29,70 @@ func TestSaveEnvFile(t *testing.T) {
|
||||
t.Fatalf("SaveEnvFile() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// testVendor implements ai.Vendor for testing purposes
|
||||
type testVendor struct {
|
||||
name string
|
||||
models []string
|
||||
}
|
||||
|
||||
func (m *testVendor) GetName() string { return m.name }
|
||||
func (m *testVendor) GetSetupDescription() string { return m.name }
|
||||
func (m *testVendor) IsConfigured() bool { return true }
|
||||
func (m *testVendor) Configure() error { return nil }
|
||||
func (m *testVendor) Setup() error { return nil }
|
||||
func (m *testVendor) SetupFillEnvFileContent(*bytes.Buffer) {}
|
||||
func (m *testVendor) ListModels() ([]string, error) { return m.models, nil }
|
||||
func (m *testVendor) SendStream([]*chat.ChatCompletionMessage, *domain.ChatOptions, chan string) error {
|
||||
return nil
|
||||
}
|
||||
func (m *testVendor) Send(context.Context, []*chat.ChatCompletionMessage, *domain.ChatOptions) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
func (m *testVendor) NeedsRawMode(string) bool { return false }
|
||||
|
||||
func TestGetChatter_WarnsOnAmbiguousModel(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
db := fsdb.NewDb(tempDir)
|
||||
|
||||
vendorA := &testVendor{name: "VendorA", models: []string{"shared-model"}}
|
||||
vendorB := &testVendor{name: "VendorB", models: []string{"shared-model"}}
|
||||
|
||||
vm := ai.NewVendorsManager()
|
||||
vm.AddVendors(vendorA, vendorB)
|
||||
|
||||
defaults := &tools.Defaults{
|
||||
PluginBase: &plugins.PluginBase{},
|
||||
Vendor: &plugins.Setting{Value: "VendorA"},
|
||||
Model: &plugins.SetupQuestion{Setting: &plugins.Setting{Value: "shared-model"}},
|
||||
ModelContextLength: &plugins.SetupQuestion{Setting: &plugins.Setting{Value: "0"}},
|
||||
}
|
||||
|
||||
registry := &PluginRegistry{Db: db, VendorManager: vm, Defaults: defaults}
|
||||
|
||||
r, w, _ := os.Pipe()
|
||||
oldStderr := os.Stderr
|
||||
os.Stderr = w
|
||||
// Redirect log output to our pipe to capture unconditional log messages
|
||||
debuglog.SetOutput(w)
|
||||
defer func() {
|
||||
os.Stderr = oldStderr
|
||||
debuglog.SetOutput(oldStderr)
|
||||
}()
|
||||
|
||||
chatter, err := registry.GetChatter("shared-model", 0, "", "", false, false)
|
||||
w.Close()
|
||||
warning, _ := io.ReadAll(r)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("GetChatter() error = %v", err)
|
||||
}
|
||||
// Verify that one of the valid vendors was selected (don't care which one due to map iteration randomness)
|
||||
vendorName := chatter.vendor.GetName()
|
||||
if vendorName != "VendorA" && vendorName != "VendorB" {
|
||||
t.Fatalf("expected vendor VendorA or VendorB, got %s", vendorName)
|
||||
}
|
||||
if !strings.Contains(string(warning), "multiple vendors provide model shared-model") {
|
||||
t.Fatalf("expected warning about multiple vendors, got %q", string(warning))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,38 +4,53 @@ import "github.com/danielmiessler/fabric/internal/chat"
|
||||
|
||||
const ChatMessageRoleMeta = "meta"
|
||||
|
||||
// Default values for chat options (must match cli/flags.go defaults)
|
||||
const (
|
||||
DefaultTemperature = 0.7
|
||||
DefaultTopP = 0.9
|
||||
DefaultPresencePenalty = 0.0
|
||||
DefaultFrequencyPenalty = 0.0
|
||||
)
|
||||
|
||||
type ChatRequest struct {
|
||||
ContextName string
|
||||
SessionName string
|
||||
PatternName string
|
||||
PatternVariables map[string]string
|
||||
Message *chat.ChatCompletionMessage
|
||||
Language string
|
||||
Meta string
|
||||
InputHasVars bool
|
||||
StrategyName string
|
||||
ContextName string
|
||||
SessionName string
|
||||
PatternName string
|
||||
PatternVariables map[string]string
|
||||
Message *chat.ChatCompletionMessage
|
||||
Language string
|
||||
Meta string
|
||||
InputHasVars bool
|
||||
NoVariableReplacement bool
|
||||
StrategyName string
|
||||
}
|
||||
|
||||
type ChatOptions struct {
|
||||
Model string
|
||||
Temperature float64
|
||||
TopP float64
|
||||
PresencePenalty float64
|
||||
FrequencyPenalty float64
|
||||
Raw bool
|
||||
Seed int
|
||||
ModelContextLength int
|
||||
MaxTokens int
|
||||
Search bool
|
||||
SearchLocation string
|
||||
ImageFile string
|
||||
ImageSize string
|
||||
ImageQuality string
|
||||
ImageCompression int
|
||||
ImageBackground string
|
||||
SuppressThink bool
|
||||
ThinkStartTag string
|
||||
ThinkEndTag string
|
||||
Model string
|
||||
Temperature float64
|
||||
TopP float64
|
||||
PresencePenalty float64
|
||||
FrequencyPenalty float64
|
||||
Raw bool
|
||||
Seed int
|
||||
Thinking ThinkingLevel
|
||||
ModelContextLength int
|
||||
MaxTokens int
|
||||
Search bool
|
||||
SearchLocation string
|
||||
ImageFile string
|
||||
ImageSize string
|
||||
ImageQuality string
|
||||
ImageCompression int
|
||||
ImageBackground string
|
||||
SuppressThink bool
|
||||
ThinkStartTag string
|
||||
ThinkEndTag string
|
||||
AudioOutput bool
|
||||
AudioFormat string
|
||||
Voice string
|
||||
Notification bool
|
||||
NotificationCommand string
|
||||
}
|
||||
|
||||
// NormalizeMessages remove empty messages and ensure messages order user-assist-user
|
||||
|
||||
34
internal/domain/thinking.go
Normal file
34
internal/domain/thinking.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package domain
|
||||
|
||||
// ThinkingLevel represents reasoning/thinking levels supported across providers.
|
||||
type ThinkingLevel string
|
||||
|
||||
const (
|
||||
ThinkingOff ThinkingLevel = "off"
|
||||
ThinkingLow ThinkingLevel = "low"
|
||||
ThinkingMedium ThinkingLevel = "medium"
|
||||
ThinkingHigh ThinkingLevel = "high"
|
||||
)
|
||||
|
||||
// ThinkingBudgets defines standardized token budgets for reasoning-enabled models.
|
||||
// The map assigns a maximum token count to each ThinkingLevel, representing the
|
||||
// amount of context or computation that can be used for reasoning at that level.
|
||||
// These values (e.g., 1024 for low, 2048 for medium, 4096 for high) are used to
|
||||
// Token budget constants for each ThinkingLevel.
|
||||
// These values are chosen to align with typical context window sizes for LLMs at different reasoning levels.
|
||||
// Adjust these if model capabilities change.
|
||||
const (
|
||||
// TokenBudgetLow is suitable for basic reasoning or smaller models (e.g., 1k context window).
|
||||
TokenBudgetLow int64 = 1024
|
||||
// TokenBudgetMedium is suitable for intermediate reasoning or mid-sized models (e.g., 2k context window).
|
||||
TokenBudgetMedium int64 = 2048
|
||||
// TokenBudgetHigh is suitable for advanced reasoning or large models (e.g., 4k context window).
|
||||
TokenBudgetHigh int64 = 4096
|
||||
)
|
||||
|
||||
// ThinkingBudgets defines standardized token budgets for reasoning-enabled models.
|
||||
var ThinkingBudgets = map[ThinkingLevel]int64{
|
||||
ThinkingLow: TokenBudgetLow,
|
||||
ThinkingMedium: TokenBudgetMedium,
|
||||
ThinkingHigh: TokenBudgetHigh,
|
||||
}
|
||||
78
internal/log/log.go
Normal file
78
internal/log/log.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Level represents the debug verbosity.
|
||||
type Level int
|
||||
|
||||
const (
|
||||
// Off disables all debug output.
|
||||
Off Level = iota
|
||||
// Basic provides minimal debugging information.
|
||||
Basic
|
||||
// Detailed provides more verbose debugging.
|
||||
Detailed
|
||||
// Trace is the most verbose level.
|
||||
Trace
|
||||
)
|
||||
|
||||
var (
|
||||
mu sync.RWMutex
|
||||
level Level = Off
|
||||
output io.Writer = os.Stderr
|
||||
)
|
||||
|
||||
// SetLevel sets the global debug level.
|
||||
func SetLevel(l Level) {
|
||||
mu.Lock()
|
||||
level = l
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
// LevelFromInt converts an int to a Level.
|
||||
func LevelFromInt(i int) Level {
|
||||
switch {
|
||||
case i <= 0:
|
||||
return Off
|
||||
case i == 1:
|
||||
return Basic
|
||||
case i == 2:
|
||||
return Detailed
|
||||
case i >= 3:
|
||||
return Trace
|
||||
default:
|
||||
return Off
|
||||
}
|
||||
}
|
||||
|
||||
// Debug writes a debug message if the global level permits.
|
||||
func Debug(l Level, format string, a ...interface{}) {
|
||||
mu.RLock()
|
||||
current := level
|
||||
w := output
|
||||
mu.RUnlock()
|
||||
if current >= l {
|
||||
fmt.Fprintf(w, "DEBUG: "+format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// Log writes a message unconditionally to stderr.
|
||||
// This is for important messages that should always be shown regardless of debug level.
|
||||
func Log(format string, a ...interface{}) {
|
||||
mu.RLock()
|
||||
w := output
|
||||
mu.RUnlock()
|
||||
fmt.Fprintf(w, format, a...)
|
||||
}
|
||||
|
||||
// SetOutput allows overriding the output destination for debug logs.
|
||||
func SetOutput(w io.Writer) {
|
||||
mu.Lock()
|
||||
output = w
|
||||
mu.Unlock()
|
||||
}
|
||||
@@ -4,12 +4,14 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/anthropics/anthropic-sdk-go"
|
||||
"github.com/anthropics/anthropic-sdk-go/option"
|
||||
"github.com/danielmiessler/fabric/internal/chat"
|
||||
"github.com/danielmiessler/fabric/internal/domain"
|
||||
debuglog "github.com/danielmiessler/fabric/internal/log"
|
||||
"github.com/danielmiessler/fabric/internal/plugins"
|
||||
"github.com/danielmiessler/fabric/internal/util"
|
||||
)
|
||||
@@ -46,6 +48,11 @@ func NewClient() (ret *Client) {
|
||||
string(anthropic.ModelClaude_3_5_Sonnet_20240620), string(anthropic.ModelClaude3OpusLatest),
|
||||
string(anthropic.ModelClaude_3_Opus_20240229), string(anthropic.ModelClaude_3_Haiku_20240307),
|
||||
string(anthropic.ModelClaudeOpus4_20250514), string(anthropic.ModelClaudeSonnet4_20250514),
|
||||
string(anthropic.ModelClaudeOpus4_1_20250805),
|
||||
}
|
||||
|
||||
ret.modelBetas = map[string][]string{
|
||||
string(anthropic.ModelClaudeSonnet4_20250514): {"context-1m-2025-08-07"},
|
||||
}
|
||||
|
||||
return
|
||||
@@ -92,6 +99,7 @@ type Client struct {
|
||||
maxTokens int
|
||||
defaultRequiredUserMessage string
|
||||
models []string
|
||||
modelBetas map[string][]string
|
||||
|
||||
client anthropic.Client
|
||||
}
|
||||
@@ -147,6 +155,26 @@ func (an *Client) ListModels() (ret []string, err error) {
|
||||
return an.models, nil
|
||||
}
|
||||
|
||||
func parseThinking(level domain.ThinkingLevel) (anthropic.ThinkingConfigParamUnion, bool) {
|
||||
lower := strings.ToLower(string(level))
|
||||
switch domain.ThinkingLevel(lower) {
|
||||
case domain.ThinkingOff:
|
||||
disabled := anthropic.NewThinkingConfigDisabledParam()
|
||||
return anthropic.ThinkingConfigParamUnion{OfDisabled: &disabled}, true
|
||||
case domain.ThinkingLow, domain.ThinkingMedium, domain.ThinkingHigh:
|
||||
if budget, ok := domain.ThinkingBudgets[domain.ThinkingLevel(lower)]; ok {
|
||||
return anthropic.ThinkingConfigParamOfEnabled(budget), true
|
||||
}
|
||||
default:
|
||||
if tokens, err := strconv.ParseInt(lower, 10, 64); err == nil {
|
||||
if tokens >= 1 && tokens <= 10000 {
|
||||
return anthropic.ThinkingConfigParamOfEnabled(tokens), true
|
||||
}
|
||||
}
|
||||
}
|
||||
return anthropic.ThinkingConfigParamUnion{}, false
|
||||
}
|
||||
|
||||
func (an *Client) SendStream(
|
||||
msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string,
|
||||
) (err error) {
|
||||
@@ -159,7 +187,17 @@ func (an *Client) SendStream(
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
stream := an.client.Messages.NewStreaming(ctx, an.buildMessageParams(messages, opts))
|
||||
params := an.buildMessageParams(messages, opts)
|
||||
betas := an.modelBetas[opts.Model]
|
||||
var reqOpts []option.RequestOption
|
||||
if len(betas) > 0 {
|
||||
reqOpts = append(reqOpts, option.WithHeader("anthropic-beta", strings.Join(betas, ",")))
|
||||
}
|
||||
stream := an.client.Messages.NewStreaming(ctx, params, reqOpts...)
|
||||
if stream.Err() != nil && len(betas) > 0 {
|
||||
debuglog.Debug(debuglog.Basic, "Anthropic beta feature %s failed: %v\n", strings.Join(betas, ","), stream.Err())
|
||||
stream = an.client.Messages.NewStreaming(ctx, params)
|
||||
}
|
||||
|
||||
for stream.Next() {
|
||||
event := stream.Current()
|
||||
@@ -181,11 +219,19 @@ func (an *Client) buildMessageParams(msgs []anthropic.MessageParam, opts *domain
|
||||
params anthropic.MessageNewParams) {
|
||||
|
||||
params = anthropic.MessageNewParams{
|
||||
Model: anthropic.Model(opts.Model),
|
||||
MaxTokens: int64(an.maxTokens),
|
||||
TopP: anthropic.Opt(opts.TopP),
|
||||
Temperature: anthropic.Opt(opts.Temperature),
|
||||
Messages: msgs,
|
||||
Model: anthropic.Model(opts.Model),
|
||||
MaxTokens: int64(an.maxTokens),
|
||||
Messages: msgs,
|
||||
}
|
||||
|
||||
// Only set one of Temperature or TopP as some models don't allow both
|
||||
// Always set temperature to ensure consistent behavior (Anthropic default is 1.0, Fabric default is 0.7)
|
||||
if opts.TopP != domain.DefaultTopP {
|
||||
// User explicitly set TopP, so use that instead of temperature
|
||||
params.TopP = anthropic.Opt(opts.TopP)
|
||||
} else {
|
||||
// Use temperature (always set to ensure Fabric's default of 0.7, not Anthropic's 1.0)
|
||||
params.Temperature = anthropic.Opt(opts.Temperature)
|
||||
}
|
||||
|
||||
// Add Claude Code spoofing system message for OAuth authentication
|
||||
@@ -217,6 +263,11 @@ func (an *Client) buildMessageParams(msgs []anthropic.MessageParam, opts *domain
|
||||
{OfWebSearchTool20250305: &webTool},
|
||||
}
|
||||
}
|
||||
|
||||
if t, ok := parseThinking(opts.Thinking); ok {
|
||||
params.Thinking = t
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -230,8 +281,21 @@ func (an *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage,
|
||||
}
|
||||
|
||||
var message *anthropic.Message
|
||||
if message, err = an.client.Messages.New(ctx, an.buildMessageParams(messages, opts)); err != nil {
|
||||
return
|
||||
params := an.buildMessageParams(messages, opts)
|
||||
betas := an.modelBetas[opts.Model]
|
||||
var reqOpts []option.RequestOption
|
||||
if len(betas) > 0 {
|
||||
reqOpts = append(reqOpts, option.WithHeader("anthropic-beta", strings.Join(betas, ",")))
|
||||
}
|
||||
if message, err = an.client.Messages.New(ctx, params, reqOpts...); err != nil {
|
||||
if len(betas) > 0 {
|
||||
debuglog.Debug(debuglog.Basic, "Anthropic beta feature %s failed: %v\n", strings.Join(betas, ","), err)
|
||||
if message, err = an.client.Messages.New(ctx, params); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var textParts []string
|
||||
|
||||
@@ -72,7 +72,8 @@ func TestBuildMessageParams_WithoutSearch(t *testing.T) {
|
||||
client := NewClient()
|
||||
opts := &domain.ChatOptions{
|
||||
Model: "claude-3-5-sonnet-latest",
|
||||
Temperature: 0.7,
|
||||
Temperature: 0.8, // Use non-default value to ensure it gets set
|
||||
TopP: domain.DefaultTopP, // Use default TopP so temperature takes precedence
|
||||
Search: false,
|
||||
}
|
||||
|
||||
@@ -90,6 +91,7 @@ func TestBuildMessageParams_WithoutSearch(t *testing.T) {
|
||||
t.Errorf("Expected model %s, got %s", opts.Model, params.Model)
|
||||
}
|
||||
|
||||
// When using non-default temperature, it should be set in params
|
||||
if params.Temperature.Value != opts.Temperature {
|
||||
t.Errorf("Expected temperature %f, got %f", opts.Temperature, params.Temperature.Value)
|
||||
}
|
||||
@@ -99,7 +101,8 @@ func TestBuildMessageParams_WithSearch(t *testing.T) {
|
||||
client := NewClient()
|
||||
opts := &domain.ChatOptions{
|
||||
Model: "claude-3-5-sonnet-latest",
|
||||
Temperature: 0.7,
|
||||
Temperature: 0.8, // Use non-default value
|
||||
TopP: domain.DefaultTopP, // Use default TopP so temperature takes precedence
|
||||
Search: true,
|
||||
}
|
||||
|
||||
@@ -135,7 +138,8 @@ func TestBuildMessageParams_WithSearchAndLocation(t *testing.T) {
|
||||
client := NewClient()
|
||||
opts := &domain.ChatOptions{
|
||||
Model: "claude-3-5-sonnet-latest",
|
||||
Temperature: 0.7,
|
||||
Temperature: 0.8, // Use non-default value
|
||||
TopP: domain.DefaultTopP, // Use default TopP so temperature takes precedence
|
||||
Search: true,
|
||||
SearchLocation: "America/Los_Angeles",
|
||||
}
|
||||
@@ -164,6 +168,15 @@ func TestBuildMessageParams_WithSearchAndLocation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestModelBetasConfiguration(t *testing.T) {
|
||||
client := NewClient()
|
||||
model := string(anthropic.ModelClaudeSonnet4_20250514)
|
||||
betas, ok := client.modelBetas[model]
|
||||
if !ok || len(betas) != 1 || betas[0] != "context-1m-2025-08-07" {
|
||||
t.Errorf("expected beta mapping for %s", model)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCitationFormatting(t *testing.T) {
|
||||
// Test the citation formatting logic by creating a mock message with citations
|
||||
message := &anthropic.Message{
|
||||
@@ -256,3 +269,59 @@ func TestCitationFormatting(t *testing.T) {
|
||||
t.Errorf("Expected 2 unique citations, got %d", citationCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMessageParams_DefaultValues(t *testing.T) {
|
||||
client := NewClient()
|
||||
|
||||
// Test with default temperature - should always set temperature unless TopP is explicitly set
|
||||
opts := &domain.ChatOptions{
|
||||
Model: "claude-3-5-sonnet-latest",
|
||||
Temperature: domain.DefaultTemperature, // 0.7 - should be set to override Anthropic's 1.0 default
|
||||
TopP: domain.DefaultTopP, // 0.9 - default, so temperature takes precedence
|
||||
Search: false,
|
||||
}
|
||||
|
||||
messages := []anthropic.MessageParam{
|
||||
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello")),
|
||||
}
|
||||
|
||||
params := client.buildMessageParams(messages, opts)
|
||||
|
||||
// Temperature should be set when using default value to override Anthropic's 1.0 default
|
||||
if params.Temperature.Value != opts.Temperature {
|
||||
t.Errorf("Expected temperature %f, got %f", opts.Temperature, params.Temperature.Value)
|
||||
}
|
||||
|
||||
// TopP should not be set when using default value (temperature takes precedence)
|
||||
if params.TopP.Value != 0 {
|
||||
t.Errorf("Expected TopP to not be set (0), but got %f", params.TopP.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMessageParams_ExplicitTopP(t *testing.T) {
|
||||
client := NewClient()
|
||||
|
||||
// Test with explicit TopP - should set TopP instead of temperature
|
||||
opts := &domain.ChatOptions{
|
||||
Model: "claude-3-5-sonnet-latest",
|
||||
Temperature: domain.DefaultTemperature, // 0.7 - ignored when TopP is explicitly set
|
||||
TopP: 0.5, // Non-default - should be set
|
||||
Search: false,
|
||||
}
|
||||
|
||||
messages := []anthropic.MessageParam{
|
||||
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello")),
|
||||
}
|
||||
|
||||
params := client.buildMessageParams(messages, opts)
|
||||
|
||||
// Temperature should not be set when TopP is explicitly set
|
||||
if params.Temperature.Value != 0 {
|
||||
t.Errorf("Expected temperature to not be set (0), but got %f", params.Temperature.Value)
|
||||
}
|
||||
|
||||
// TopP should be set when using non-default value
|
||||
if params.TopP.Value != opts.TopP {
|
||||
t.Errorf("Expected TopP %f, got %f", opts.TopP, params.TopP.Value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
debuglog "github.com/danielmiessler/fabric/internal/log"
|
||||
"github.com/danielmiessler/fabric/internal/util"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
@@ -46,8 +46,13 @@ func (t *OAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
// Add OAuth Bearer token
|
||||
newReq.Header.Set("Authorization", "Bearer "+token)
|
||||
|
||||
// Add the anthropic-beta header for OAuth
|
||||
newReq.Header.Set("anthropic-beta", "oauth-2025-04-20")
|
||||
// Add the anthropic-beta header for OAuth, preserving existing betas
|
||||
existing := newReq.Header.Get("anthropic-beta")
|
||||
beta := "oauth-2025-04-20"
|
||||
if existing != "" {
|
||||
beta = existing + "," + beta
|
||||
}
|
||||
newReq.Header.Set("anthropic-beta", beta)
|
||||
|
||||
// Set User-Agent to match AI SDK exactly
|
||||
newReq.Header.Set("User-Agent", "ai-sdk/anthropic")
|
||||
@@ -72,7 +77,7 @@ func (t *OAuthTransport) getValidToken(tokenIdentifier string) (string, error) {
|
||||
}
|
||||
// If no token exists, run OAuth flow
|
||||
if token == nil {
|
||||
fmt.Fprintln(os.Stderr, "No OAuth token found, initiating authentication...")
|
||||
debuglog.Log("No OAuth token found, initiating authentication...\n")
|
||||
newAccessToken, err := RunOAuthFlow(tokenIdentifier)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to authenticate: %w", err)
|
||||
@@ -82,11 +87,11 @@ func (t *OAuthTransport) getValidToken(tokenIdentifier string) (string, error) {
|
||||
|
||||
// Check if token needs refresh (5 minute buffer)
|
||||
if token.IsExpired(5) {
|
||||
fmt.Fprintln(os.Stderr, "OAuth token expired, refreshing...")
|
||||
debuglog.Log("OAuth token expired, refreshing...\n")
|
||||
newAccessToken, err := RefreshToken(tokenIdentifier)
|
||||
if err != nil {
|
||||
// If refresh fails, try re-authentication
|
||||
fmt.Fprintln(os.Stderr, "Token refresh failed, re-authenticating...")
|
||||
debuglog.Log("Token refresh failed, re-authenticating...\n")
|
||||
newAccessToken, err = RunOAuthFlow(tokenIdentifier)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to refresh or re-authenticate: %w", err)
|
||||
@@ -138,13 +143,13 @@ func RunOAuthFlow(tokenIdentifier string) (token string, err error) {
|
||||
if err == nil && existingToken != nil {
|
||||
// If token exists but is expired, try refreshing first
|
||||
if existingToken.IsExpired(5) {
|
||||
fmt.Fprintln(os.Stderr, "Found expired OAuth token, attempting refresh...")
|
||||
debuglog.Log("Found expired OAuth token, attempting refresh...\n")
|
||||
refreshedToken, refreshErr := RefreshToken(tokenIdentifier)
|
||||
if refreshErr == nil {
|
||||
fmt.Fprintln(os.Stderr, "Token refresh successful")
|
||||
debuglog.Log("Token refresh successful\n")
|
||||
return refreshedToken, nil
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Token refresh failed (%v), proceeding with full OAuth flow...\n", refreshErr)
|
||||
debuglog.Log("Token refresh failed (%v), proceeding with full OAuth flow...\n", refreshErr)
|
||||
} else {
|
||||
// Token exists and is still valid
|
||||
return existingToken.AccessToken, nil
|
||||
@@ -171,10 +176,10 @@ func RunOAuthFlow(tokenIdentifier string) (token string, err error) {
|
||||
oauth2.SetAuthURLParam("state", verifier),
|
||||
)
|
||||
|
||||
fmt.Fprintln(os.Stderr, "Open the following URL in your browser. Fabric would like to authorize:")
|
||||
fmt.Fprintln(os.Stderr, authURL)
|
||||
debuglog.Log("Open the following URL in your browser. Fabric would like to authorize:\n")
|
||||
debuglog.Log("%s\n", authURL)
|
||||
openBrowser(authURL)
|
||||
fmt.Fprint(os.Stderr, "Paste the authorization code here: ")
|
||||
debuglog.Log("Paste the authorization code here: ")
|
||||
var code string
|
||||
fmt.Scanln(&code)
|
||||
parts := strings.SplitN(code, "#", 2)
|
||||
|
||||
@@ -153,7 +153,7 @@ func (c *BedrockClient) ListModels() ([]string, error) {
|
||||
return models, nil
|
||||
}
|
||||
|
||||
// SendStream sends the messages to the the Bedrock ConverseStream API
|
||||
// SendStream sends the messages to the Bedrock ConverseStream API
|
||||
func (c *BedrockClient) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string) (err error) {
|
||||
// Ensure channel is closed on all exit paths to prevent goroutine leaks
|
||||
defer func() {
|
||||
|
||||
@@ -87,6 +87,9 @@ func (c *Client) formatOptions(opts *domain.ChatOptions) string {
|
||||
if opts.ImageFile != "" {
|
||||
builder.WriteString(fmt.Sprintf("ImageFile: %s\n", opts.ImageFile))
|
||||
}
|
||||
if opts.Thinking != "" {
|
||||
builder.WriteString(fmt.Sprintf("Thinking: %s\n", string(opts.Thinking)))
|
||||
}
|
||||
if opts.SuppressThink {
|
||||
builder.WriteString("SuppressThink: enabled\n")
|
||||
builder.WriteString(fmt.Sprintf("Thinking Start Tag: %s\n", opts.ThinkStartTag))
|
||||
|
||||
@@ -13,6 +13,7 @@ func NewClient() (ret *Client) {
|
||||
ret = &Client{}
|
||||
ret.Client = openai.NewClientCompatibleNoSetupQuestions("Exolab", ret.configure)
|
||||
|
||||
ret.ApiKey = ret.AddSetupQuestion("API Key", false)
|
||||
ret.ApiBaseURL = ret.AddSetupQuestion("API Base URL", true)
|
||||
ret.ApiBaseURL.Value = "http://localhost:52415"
|
||||
|
||||
|
||||
@@ -1,21 +1,50 @@
|
||||
package gemini
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/chat"
|
||||
"github.com/danielmiessler/fabric/internal/plugins"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/domain"
|
||||
"github.com/google/generative-ai-go/genai"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/genai"
|
||||
)
|
||||
|
||||
const modelsNamePrefix = "models/"
|
||||
// WAV audio constants
|
||||
const (
|
||||
DefaultChannels = 1
|
||||
DefaultSampleRate = 24000
|
||||
DefaultBitsPerSample = 16
|
||||
WAVHeaderSize = 44
|
||||
RIFFHeaderSize = 36
|
||||
MaxAudioDataSize = 100 * 1024 * 1024 // 100MB limit for security
|
||||
MinAudioDataSize = 44 // Minimum viable audio data
|
||||
AudioDataPrefix = "FABRIC_AUDIO_DATA:"
|
||||
)
|
||||
|
||||
const (
|
||||
citationHeader = "\n\n## Sources\n\n"
|
||||
citationSeparator = "\n"
|
||||
citationFormat = "- [%s](%s)"
|
||||
|
||||
errInvalidLocationFormat = "invalid search location format %q: must be timezone (e.g., 'America/Los_Angeles') or language code (e.g., 'en-US')"
|
||||
locationSeparator = "/"
|
||||
langCodeSeparator = "_"
|
||||
langCodeNormalizedSep = "-"
|
||||
|
||||
modelPrefix = "models/"
|
||||
modelTypeTTS = "tts"
|
||||
modelTypePreviewTTS = "preview-tts"
|
||||
modelTypeTextToSpeech = "text-to-speech"
|
||||
)
|
||||
|
||||
var langCodeRegex = regexp.MustCompile(`^[a-z]{2}(-[A-Z]{2})?$`)
|
||||
|
||||
func NewClient() (ret *Client) {
|
||||
vendorName := "Gemini"
|
||||
@@ -39,107 +68,102 @@ type Client struct {
|
||||
func (o *Client) ListModels() (ret []string, err error) {
|
||||
ctx := context.Background()
|
||||
var client *genai.Client
|
||||
if client, err = genai.NewClient(ctx, option.WithAPIKey(o.ApiKey.Value)); err != nil {
|
||||
if client, err = genai.NewClient(ctx, &genai.ClientConfig{
|
||||
APIKey: o.ApiKey.Value,
|
||||
Backend: genai.BackendGeminiAPI,
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
iter := client.ListModels(ctx)
|
||||
for {
|
||||
var resp *genai.ModelInfo
|
||||
if resp, err = iter.Next(); err != nil {
|
||||
if errors.Is(err, iterator.Done) {
|
||||
err = nil
|
||||
}
|
||||
break
|
||||
}
|
||||
// List available models using the correct API
|
||||
resp, err := client.Models.List(ctx, &genai.ListModelsConfig{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := o.buildModelNameSimple(resp.Name)
|
||||
ret = append(ret, name)
|
||||
for _, model := range resp.Items {
|
||||
// Strip the "models/" prefix for user convenience
|
||||
modelName := strings.TrimPrefix(model.Name, "models/")
|
||||
ret = append(ret, modelName)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions) (ret string, err error) {
|
||||
systemInstruction, messages := toMessages(msgs)
|
||||
// Check if this is a TTS model request
|
||||
if o.isTTSModel(opts.Model) {
|
||||
if !opts.AudioOutput {
|
||||
err = fmt.Errorf("TTS model '%s' requires audio output. Please specify an audio output file with -o flag ending in .wav", opts.Model)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle TTS generation
|
||||
return o.generateTTSAudio(ctx, msgs, opts)
|
||||
}
|
||||
|
||||
// Regular text generation
|
||||
var client *genai.Client
|
||||
if client, err = genai.NewClient(ctx, option.WithAPIKey(o.ApiKey.Value)); err != nil {
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
model := client.GenerativeModel(o.buildModelNameFull(opts.Model))
|
||||
model.SetTemperature(float32(opts.Temperature))
|
||||
model.SetTopP(float32(opts.TopP))
|
||||
model.SystemInstruction = systemInstruction
|
||||
|
||||
var response *genai.GenerateContentResponse
|
||||
if response, err = model.GenerateContent(ctx, messages...); err != nil {
|
||||
if client, err = genai.NewClient(ctx, &genai.ClientConfig{
|
||||
APIKey: o.ApiKey.Value,
|
||||
Backend: genai.BackendGeminiAPI,
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ret = o.extractText(response)
|
||||
// Convert messages to new SDK format
|
||||
contents := o.convertMessages(msgs)
|
||||
|
||||
cfg, err := o.buildGenerateContentConfig(opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Generate content with optional tools
|
||||
response, err := client.Models.GenerateContent(ctx, o.buildModelNameFull(opts.Model), contents, cfg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Extract text from response
|
||||
ret = o.extractTextFromResponse(response)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Client) buildModelNameSimple(fullModelName string) string {
|
||||
return strings.TrimPrefix(fullModelName, modelsNamePrefix)
|
||||
}
|
||||
|
||||
func (o *Client) buildModelNameFull(modelName string) string {
|
||||
return fmt.Sprintf("%v%v", modelsNamePrefix, modelName)
|
||||
}
|
||||
|
||||
func (o *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string) (err error) {
|
||||
ctx := context.Background()
|
||||
var client *genai.Client
|
||||
if client, err = genai.NewClient(ctx, option.WithAPIKey(o.ApiKey.Value)); err != nil {
|
||||
if client, err = genai.NewClient(ctx, &genai.ClientConfig{
|
||||
APIKey: o.ApiKey.Value,
|
||||
Backend: genai.BackendGeminiAPI,
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
systemInstruction, messages := toMessages(msgs)
|
||||
// Convert messages to new SDK format
|
||||
contents := o.convertMessages(msgs)
|
||||
|
||||
model := client.GenerativeModel(o.buildModelNameFull(opts.Model))
|
||||
model.SetTemperature(float32(opts.Temperature))
|
||||
model.SetTopP(float32(opts.TopP))
|
||||
model.SystemInstruction = systemInstruction
|
||||
cfg, err := o.buildGenerateContentConfig(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
iter := model.GenerateContentStream(ctx, messages...)
|
||||
for {
|
||||
if resp, iterErr := iter.Next(); iterErr == nil {
|
||||
for _, candidate := range resp.Candidates {
|
||||
if candidate.Content != nil {
|
||||
for _, part := range candidate.Content.Parts {
|
||||
if text, ok := part.(genai.Text); ok {
|
||||
channel <- string(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !errors.Is(iterErr, iterator.Done) {
|
||||
channel <- fmt.Sprintf("%v\n", iterErr)
|
||||
}
|
||||
// Generate streaming content with optional tools
|
||||
stream := client.Models.GenerateContentStream(ctx, o.buildModelNameFull(opts.Model), contents, cfg)
|
||||
|
||||
for response, err := range stream {
|
||||
if err != nil {
|
||||
channel <- fmt.Sprintf("Error: %v\n", err)
|
||||
close(channel)
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Client) extractText(response *genai.GenerateContentResponse) (ret string) {
|
||||
for _, candidate := range response.Candidates {
|
||||
if candidate.Content == nil {
|
||||
break
|
||||
}
|
||||
for _, part := range candidate.Content.Parts {
|
||||
if text, ok := part.(genai.Text); ok {
|
||||
ret += string(text)
|
||||
}
|
||||
text := o.extractTextFromResponse(response)
|
||||
if text != "" {
|
||||
channel <- text
|
||||
}
|
||||
}
|
||||
close(channel)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -147,18 +171,377 @@ func (o *Client) NeedsRawMode(modelName string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func toMessages(msgs []*chat.ChatCompletionMessage) (systemInstruction *genai.Content, messages []genai.Part) {
|
||||
if len(msgs) >= 2 {
|
||||
systemInstruction = &genai.Content{
|
||||
Parts: []genai.Part{
|
||||
genai.Text(msgs[0].Content),
|
||||
},
|
||||
func parseThinkingConfig(level domain.ThinkingLevel) (*genai.ThinkingConfig, bool) {
|
||||
lower := strings.ToLower(strings.TrimSpace(string(level)))
|
||||
switch domain.ThinkingLevel(lower) {
|
||||
case "", domain.ThinkingOff:
|
||||
return nil, false
|
||||
case domain.ThinkingLow, domain.ThinkingMedium, domain.ThinkingHigh:
|
||||
if budget, ok := domain.ThinkingBudgets[domain.ThinkingLevel(lower)]; ok {
|
||||
b := int32(budget)
|
||||
return &genai.ThinkingConfig{IncludeThoughts: true, ThinkingBudget: &b}, true
|
||||
}
|
||||
for _, msg := range msgs[1:] {
|
||||
messages = append(messages, genai.Text(msg.Content))
|
||||
default:
|
||||
if tokens, err := strconv.ParseInt(lower, 10, 32); err == nil && tokens > 0 {
|
||||
t := int32(tokens)
|
||||
return &genai.ThinkingConfig{IncludeThoughts: true, ThinkingBudget: &t}, true
|
||||
}
|
||||
} else {
|
||||
messages = append(messages, genai.Text(msgs[0].Content))
|
||||
}
|
||||
return
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// buildGenerateContentConfig constructs the generation config with optional tools.
|
||||
// When search is enabled it injects the Google Search tool. The optional search
|
||||
// location accepts either:
|
||||
// - A timezone in the format "Continent/City" (e.g., "America/Los_Angeles")
|
||||
// - An ISO language code "ll" or "ll-CC" (e.g., "en" or "en-US")
|
||||
//
|
||||
// Underscores are normalized to hyphens. Returns an error if the location is
|
||||
// invalid.
|
||||
func (o *Client) buildGenerateContentConfig(opts *domain.ChatOptions) (*genai.GenerateContentConfig, error) {
|
||||
temperature := float32(opts.Temperature)
|
||||
topP := float32(opts.TopP)
|
||||
cfg := &genai.GenerateContentConfig{
|
||||
Temperature: &temperature,
|
||||
TopP: &topP,
|
||||
MaxOutputTokens: int32(opts.ModelContextLength),
|
||||
}
|
||||
|
||||
if opts.Search {
|
||||
cfg.Tools = []*genai.Tool{{GoogleSearch: &genai.GoogleSearch{}}}
|
||||
if loc := opts.SearchLocation; loc != "" {
|
||||
if isValidLocationFormat(loc) {
|
||||
loc = normalizeLocation(loc)
|
||||
cfg.ToolConfig = &genai.ToolConfig{
|
||||
RetrievalConfig: &genai.RetrievalConfig{LanguageCode: loc},
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf(errInvalidLocationFormat, loc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if tc, ok := parseThinkingConfig(opts.Thinking); ok {
|
||||
cfg.ThinkingConfig = tc
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// buildModelNameFull adds the "models/" prefix for API calls
|
||||
func (o *Client) buildModelNameFull(modelName string) string {
|
||||
if strings.HasPrefix(modelName, modelPrefix) {
|
||||
return modelName
|
||||
}
|
||||
return modelPrefix + modelName
|
||||
}
|
||||
|
||||
func isValidLocationFormat(location string) bool {
|
||||
if strings.Contains(location, locationSeparator) {
|
||||
parts := strings.Split(location, locationSeparator)
|
||||
return len(parts) == 2 && parts[0] != "" && parts[1] != ""
|
||||
}
|
||||
return isValidLanguageCode(location)
|
||||
}
|
||||
|
||||
func normalizeLocation(location string) string {
|
||||
if strings.Contains(location, locationSeparator) {
|
||||
return location
|
||||
}
|
||||
return strings.Replace(location, langCodeSeparator, langCodeNormalizedSep, 1)
|
||||
}
|
||||
|
||||
// isValidLanguageCode reports whether the input is an ISO 639-1 language code
|
||||
// optionally followed by an ISO 3166-1 country code. Underscores are
|
||||
// normalized to hyphens before validation.
|
||||
func isValidLanguageCode(code string) bool {
|
||||
normalized := strings.Replace(code, langCodeSeparator, langCodeNormalizedSep, 1)
|
||||
parts := strings.Split(normalized, langCodeNormalizedSep)
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
return langCodeRegex.MatchString(strings.ToLower(parts[0]))
|
||||
case 2:
|
||||
formatted := strings.ToLower(parts[0]) + langCodeNormalizedSep + strings.ToUpper(parts[1])
|
||||
return langCodeRegex.MatchString(formatted)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// isTTSModel checks if the model is a text-to-speech model
|
||||
func (o *Client) isTTSModel(modelName string) bool {
|
||||
lowerModel := strings.ToLower(modelName)
|
||||
return strings.Contains(lowerModel, modelTypeTTS) ||
|
||||
strings.Contains(lowerModel, modelTypePreviewTTS) ||
|
||||
strings.Contains(lowerModel, modelTypeTextToSpeech)
|
||||
}
|
||||
|
||||
// extractTextForTTS extracts text content from chat messages for TTS generation
|
||||
func (o *Client) extractTextForTTS(msgs []*chat.ChatCompletionMessage) (string, error) {
|
||||
for i := len(msgs) - 1; i >= 0; i-- {
|
||||
if msgs[i].Role == chat.ChatMessageRoleUser && msgs[i].Content != "" {
|
||||
return msgs[i].Content, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("no text content found for TTS generation")
|
||||
}
|
||||
|
||||
// createGenaiClient creates a new GenAI client for TTS operations
|
||||
func (o *Client) createGenaiClient(ctx context.Context) (*genai.Client, error) {
|
||||
return genai.NewClient(ctx, &genai.ClientConfig{
|
||||
APIKey: o.ApiKey.Value,
|
||||
Backend: genai.BackendGeminiAPI,
|
||||
})
|
||||
}
|
||||
|
||||
// generateTTSAudio handles TTS audio generation using the new SDK
|
||||
func (o *Client) generateTTSAudio(ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions) (ret string, err error) {
|
||||
textToSpeak, err := o.extractTextForTTS(msgs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Validate voice name before making API call
|
||||
if opts.Voice != "" && !IsValidGeminiVoice(opts.Voice) {
|
||||
validVoices := GetGeminiVoiceNames()
|
||||
return "", fmt.Errorf("invalid voice '%s'. Valid voices are: %v", opts.Voice, validVoices)
|
||||
}
|
||||
|
||||
client, err := o.createGenaiClient(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return o.performTTSGeneration(ctx, client, textToSpeak, opts)
|
||||
}
|
||||
|
||||
// performTTSGeneration performs the actual TTS generation and audio processing
|
||||
func (o *Client) performTTSGeneration(ctx context.Context, client *genai.Client, textToSpeak string, opts *domain.ChatOptions) (string, error) {
|
||||
|
||||
// Create content for TTS
|
||||
contents := []*genai.Content{{
|
||||
Parts: []*genai.Part{{Text: textToSpeak}},
|
||||
}}
|
||||
|
||||
// Configure for TTS generation
|
||||
voiceName := opts.Voice
|
||||
if voiceName == "" {
|
||||
voiceName = "Kore" // Default voice if none specified
|
||||
}
|
||||
|
||||
config := &genai.GenerateContentConfig{
|
||||
ResponseModalities: []string{"AUDIO"},
|
||||
SpeechConfig: &genai.SpeechConfig{
|
||||
VoiceConfig: &genai.VoiceConfig{
|
||||
PrebuiltVoiceConfig: &genai.PrebuiltVoiceConfig{
|
||||
VoiceName: voiceName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Generate TTS content
|
||||
response, err := client.Models.GenerateContent(ctx, o.buildModelNameFull(opts.Model), contents, config)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("TTS generation failed: %w", err)
|
||||
}
|
||||
|
||||
// Extract and process audio data
|
||||
if len(response.Candidates) > 0 && response.Candidates[0].Content != nil && len(response.Candidates[0].Content.Parts) > 0 {
|
||||
part := response.Candidates[0].Content.Parts[0]
|
||||
if part.InlineData != nil && len(part.InlineData.Data) > 0 {
|
||||
// Validate audio data format and size
|
||||
if part.InlineData.MIMEType != "" && !strings.HasPrefix(part.InlineData.MIMEType, "audio/") {
|
||||
return "", fmt.Errorf("unexpected data type: %s, expected audio data", part.InlineData.MIMEType)
|
||||
}
|
||||
|
||||
pcmData := part.InlineData.Data
|
||||
if len(pcmData) < MinAudioDataSize {
|
||||
return "", fmt.Errorf("audio data too small: %d bytes, minimum required: %d", len(pcmData), MinAudioDataSize)
|
||||
}
|
||||
|
||||
// Generate WAV file with proper headers and return the binary data
|
||||
wavData, err := o.generateWAVFile(pcmData)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate WAV file: %w", err)
|
||||
}
|
||||
|
||||
// Validate generated WAV data
|
||||
if len(wavData) < WAVHeaderSize {
|
||||
return "", fmt.Errorf("generated WAV data is invalid: %d bytes, minimum required: %d", len(wavData), WAVHeaderSize)
|
||||
}
|
||||
|
||||
// Store the binary audio data in a special format that the CLI can detect
|
||||
// Use more efficient string concatenation
|
||||
return AudioDataPrefix + string(wavData), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no audio data received from TTS model")
|
||||
}
|
||||
|
||||
// generateWAVFile creates WAV data from PCM data with proper headers
|
||||
func (o *Client) generateWAVFile(pcmData []byte) ([]byte, error) {
|
||||
// Validate input size to prevent potential security issues
|
||||
if len(pcmData) == 0 {
|
||||
return nil, fmt.Errorf("empty PCM data provided")
|
||||
}
|
||||
if len(pcmData) > MaxAudioDataSize {
|
||||
return nil, fmt.Errorf("PCM data too large: %d bytes, maximum allowed: %d", len(pcmData), MaxAudioDataSize)
|
||||
}
|
||||
|
||||
// WAV file parameters (Gemini TTS default specs)
|
||||
channels := DefaultChannels
|
||||
sampleRate := DefaultSampleRate
|
||||
bitsPerSample := DefaultBitsPerSample
|
||||
|
||||
// Calculate required values
|
||||
byteRate := sampleRate * channels * bitsPerSample / 8
|
||||
blockAlign := channels * bitsPerSample / 8
|
||||
dataLen := uint32(len(pcmData))
|
||||
riffSize := RIFFHeaderSize + dataLen
|
||||
|
||||
// Pre-allocate buffer with known size for better performance
|
||||
totalSize := int(riffSize + 8) // +8 for RIFF header
|
||||
buf := bytes.NewBuffer(make([]byte, 0, totalSize))
|
||||
|
||||
// RIFF header
|
||||
buf.WriteString("RIFF")
|
||||
binary.Write(buf, binary.LittleEndian, riffSize)
|
||||
buf.WriteString("WAVE")
|
||||
|
||||
// fmt chunk
|
||||
buf.WriteString("fmt ")
|
||||
binary.Write(buf, binary.LittleEndian, uint32(16)) // subchunk1Size
|
||||
binary.Write(buf, binary.LittleEndian, uint16(1)) // audioFormat = PCM
|
||||
binary.Write(buf, binary.LittleEndian, uint16(channels)) // numChannels
|
||||
binary.Write(buf, binary.LittleEndian, uint32(sampleRate)) // sampleRate
|
||||
binary.Write(buf, binary.LittleEndian, uint32(byteRate)) // byteRate
|
||||
binary.Write(buf, binary.LittleEndian, uint16(blockAlign)) // blockAlign
|
||||
binary.Write(buf, binary.LittleEndian, uint16(bitsPerSample)) // bitsPerSample
|
||||
|
||||
// data chunk
|
||||
buf.WriteString("data")
|
||||
binary.Write(buf, binary.LittleEndian, dataLen)
|
||||
|
||||
// Write PCM data to buffer
|
||||
buf.Write(pcmData)
|
||||
|
||||
// Validate generated WAV data
|
||||
result := buf.Bytes()
|
||||
if len(result) < WAVHeaderSize {
|
||||
return nil, fmt.Errorf("generated WAV data is invalid: %d bytes, minimum required: %d", len(result), WAVHeaderSize)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// convertMessages converts fabric chat messages to genai Content format
|
||||
func (o *Client) convertMessages(msgs []*chat.ChatCompletionMessage) []*genai.Content {
|
||||
var contents []*genai.Content
|
||||
|
||||
for _, msg := range msgs {
|
||||
content := &genai.Content{Parts: []*genai.Part{}}
|
||||
|
||||
switch msg.Role {
|
||||
case chat.ChatMessageRoleAssistant:
|
||||
content.Role = "model"
|
||||
case chat.ChatMessageRoleUser:
|
||||
content.Role = "user"
|
||||
case chat.ChatMessageRoleSystem, chat.ChatMessageRoleDeveloper, chat.ChatMessageRoleFunction, chat.ChatMessageRoleTool:
|
||||
// Gemini's API only accepts "user" and "model" roles.
|
||||
// Map all other roles to "user" to preserve instruction context.
|
||||
content.Role = "user"
|
||||
default:
|
||||
content.Role = "user"
|
||||
}
|
||||
|
||||
if msg.Content != "" {
|
||||
content.Parts = append(content.Parts, &genai.Part{Text: msg.Content})
|
||||
}
|
||||
|
||||
// Handle multi-content messages (images, etc.)
|
||||
for _, part := range msg.MultiContent {
|
||||
switch part.Type {
|
||||
case chat.ChatMessagePartTypeText:
|
||||
content.Parts = append(content.Parts, &genai.Part{Text: part.Text})
|
||||
case chat.ChatMessagePartTypeImageURL:
|
||||
// TODO: Handle image URLs if needed
|
||||
// This would require downloading and converting to inline data
|
||||
}
|
||||
}
|
||||
|
||||
contents = append(contents, content)
|
||||
}
|
||||
|
||||
return contents
|
||||
}
|
||||
|
||||
// extractTextFromResponse extracts text content from the response and appends
|
||||
// any web citations in a standardized format.
|
||||
func (o *Client) extractTextFromResponse(response *genai.GenerateContentResponse) string {
|
||||
if response == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
text := o.extractTextParts(response)
|
||||
citations := o.extractCitations(response)
|
||||
if len(citations) > 0 {
|
||||
return text + citationHeader + strings.Join(citations, citationSeparator)
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
func (o *Client) extractTextParts(response *genai.GenerateContentResponse) string {
|
||||
var builder strings.Builder
|
||||
for _, candidate := range response.Candidates {
|
||||
if candidate == nil || candidate.Content == nil {
|
||||
continue
|
||||
}
|
||||
for _, part := range candidate.Content.Parts {
|
||||
if part != nil && part.Text != "" {
|
||||
builder.WriteString(part.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func (o *Client) extractCitations(response *genai.GenerateContentResponse) []string {
|
||||
if response == nil || len(response.Candidates) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
citationMap := make(map[string]bool)
|
||||
var citations []string
|
||||
for _, candidate := range response.Candidates {
|
||||
if candidate == nil || candidate.GroundingMetadata == nil {
|
||||
continue
|
||||
}
|
||||
chunks := candidate.GroundingMetadata.GroundingChunks
|
||||
if len(chunks) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, chunk := range chunks {
|
||||
if chunk == nil || chunk.Web == nil {
|
||||
continue
|
||||
}
|
||||
uri := chunk.Web.URI
|
||||
title := chunk.Web.Title
|
||||
if uri == "" || title == "" {
|
||||
continue
|
||||
}
|
||||
var keyBuilder strings.Builder
|
||||
keyBuilder.WriteString(uri)
|
||||
keyBuilder.WriteByte('|')
|
||||
keyBuilder.WriteString(title)
|
||||
key := keyBuilder.String()
|
||||
if !citationMap[key] {
|
||||
citationMap[key] = true
|
||||
citationText := fmt.Sprintf(citationFormat, title, uri)
|
||||
citations = append(citations, citationText)
|
||||
}
|
||||
}
|
||||
}
|
||||
return citations
|
||||
}
|
||||
|
||||
@@ -1,34 +1,46 @@
|
||||
package gemini
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/generative-ai-go/genai"
|
||||
"google.golang.org/genai"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/chat"
|
||||
"github.com/danielmiessler/fabric/internal/domain"
|
||||
)
|
||||
|
||||
// Test generated using Keploy
|
||||
func TestBuildModelNameSimple(t *testing.T) {
|
||||
// Test buildModelNameFull method
|
||||
func TestBuildModelNameFull(t *testing.T) {
|
||||
client := &Client{}
|
||||
fullModelName := "models/chat-bison-001"
|
||||
expected := "chat-bison-001"
|
||||
|
||||
result := client.buildModelNameSimple(fullModelName)
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"chat-bison-001", "models/chat-bison-001"},
|
||||
{"models/chat-bison-001", "models/chat-bison-001"},
|
||||
{"gemini-2.5-flash-preview-tts", "models/gemini-2.5-flash-preview-tts"},
|
||||
}
|
||||
|
||||
if result != expected {
|
||||
t.Errorf("Expected %v, got %v", expected, result)
|
||||
for _, test := range tests {
|
||||
result := client.buildModelNameFull(test.input)
|
||||
if result != test.expected {
|
||||
t.Errorf("For input %v, expected %v, got %v", test.input, test.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test generated using Keploy
|
||||
func TestExtractText(t *testing.T) {
|
||||
// Test extractTextFromResponse method
|
||||
func TestExtractTextFromResponse(t *testing.T) {
|
||||
client := &Client{}
|
||||
response := &genai.GenerateContentResponse{
|
||||
Candidates: []*genai.Candidate{
|
||||
{
|
||||
Content: &genai.Content{
|
||||
Parts: []genai.Part{
|
||||
genai.Text("Hello, "),
|
||||
genai.Text("world!"),
|
||||
Parts: []*genai.Part{
|
||||
{Text: "Hello, "},
|
||||
{Text: "world!"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -36,9 +48,212 @@ func TestExtractText(t *testing.T) {
|
||||
}
|
||||
expected := "Hello, world!"
|
||||
|
||||
result := client.extractText(response)
|
||||
result := client.extractTextFromResponse(response)
|
||||
|
||||
if result != expected {
|
||||
t.Errorf("Expected %v, got %v", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractTextFromResponse_Nil(t *testing.T) {
|
||||
client := &Client{}
|
||||
if got := client.extractTextFromResponse(nil); got != "" {
|
||||
t.Fatalf("expected empty string, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractTextFromResponse_EmptyGroundingChunks(t *testing.T) {
|
||||
client := &Client{}
|
||||
response := &genai.GenerateContentResponse{
|
||||
Candidates: []*genai.Candidate{
|
||||
{
|
||||
Content: &genai.Content{Parts: []*genai.Part{{Text: "Hello"}}},
|
||||
GroundingMetadata: &genai.GroundingMetadata{GroundingChunks: nil},
|
||||
},
|
||||
},
|
||||
}
|
||||
if got := client.extractTextFromResponse(response); got != "Hello" {
|
||||
t.Fatalf("expected 'Hello', got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildGenerateContentConfig_WithSearch(t *testing.T) {
|
||||
client := &Client{}
|
||||
opts := &domain.ChatOptions{Search: true}
|
||||
|
||||
cfg, err := client.buildGenerateContentConfig(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if cfg.Tools == nil || len(cfg.Tools) != 1 || cfg.Tools[0].GoogleSearch == nil {
|
||||
t.Errorf("expected google search tool to be included")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildGenerateContentConfig_WithSearchAndLocation(t *testing.T) {
|
||||
client := &Client{}
|
||||
opts := &domain.ChatOptions{Search: true, SearchLocation: "America/Los_Angeles"}
|
||||
|
||||
cfg, err := client.buildGenerateContentConfig(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if cfg.ToolConfig == nil || cfg.ToolConfig.RetrievalConfig == nil {
|
||||
t.Fatalf("expected retrieval config when search location provided")
|
||||
}
|
||||
if cfg.ToolConfig.RetrievalConfig.LanguageCode != opts.SearchLocation {
|
||||
t.Errorf("expected language code %s, got %s", opts.SearchLocation, cfg.ToolConfig.RetrievalConfig.LanguageCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildGenerateContentConfig_InvalidLocation(t *testing.T) {
|
||||
client := &Client{}
|
||||
opts := &domain.ChatOptions{Search: true, SearchLocation: "invalid"}
|
||||
|
||||
_, err := client.buildGenerateContentConfig(opts)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for invalid location")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildGenerateContentConfig_LanguageCodeNormalization(t *testing.T) {
|
||||
client := &Client{}
|
||||
opts := &domain.ChatOptions{Search: true, SearchLocation: "en_US"}
|
||||
|
||||
cfg, err := client.buildGenerateContentConfig(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if cfg.ToolConfig == nil || cfg.ToolConfig.RetrievalConfig.LanguageCode != "en-US" {
|
||||
t.Fatalf("expected normalized language code 'en-US', got %+v", cfg.ToolConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildGenerateContentConfig_Thinking(t *testing.T) {
|
||||
client := &Client{}
|
||||
opts := &domain.ChatOptions{Thinking: domain.ThinkingLow}
|
||||
|
||||
cfg, err := client.buildGenerateContentConfig(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if cfg.ThinkingConfig == nil || !cfg.ThinkingConfig.IncludeThoughts {
|
||||
t.Fatalf("expected thinking config with thoughts included")
|
||||
}
|
||||
if cfg.ThinkingConfig.ThinkingBudget == nil || *cfg.ThinkingConfig.ThinkingBudget != int32(domain.TokenBudgetLow) {
|
||||
t.Errorf("expected thinking budget %d, got %+v", domain.TokenBudgetLow, cfg.ThinkingConfig.ThinkingBudget)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildGenerateContentConfig_ThinkingTokens(t *testing.T) {
|
||||
client := &Client{}
|
||||
opts := &domain.ChatOptions{Thinking: domain.ThinkingLevel("123")}
|
||||
|
||||
cfg, err := client.buildGenerateContentConfig(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if cfg.ThinkingConfig == nil || cfg.ThinkingConfig.ThinkingBudget == nil {
|
||||
t.Fatalf("expected thinking config with budget")
|
||||
}
|
||||
if *cfg.ThinkingConfig.ThinkingBudget != 123 {
|
||||
t.Errorf("expected thinking budget 123, got %d", *cfg.ThinkingConfig.ThinkingBudget)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCitationFormatting(t *testing.T) {
|
||||
client := &Client{}
|
||||
response := &genai.GenerateContentResponse{
|
||||
Candidates: []*genai.Candidate{
|
||||
{
|
||||
Content: &genai.Content{Parts: []*genai.Part{{Text: "Based on recent research, AI is advancing rapidly."}}},
|
||||
GroundingMetadata: &genai.GroundingMetadata{
|
||||
GroundingChunks: []*genai.GroundingChunk{
|
||||
{Web: &genai.GroundingChunkWeb{URI: "https://example.com/ai", Title: "AI Research"}},
|
||||
{Web: &genai.GroundingChunkWeb{URI: "https://news.com/tech", Title: "Tech News"}},
|
||||
{Web: &genai.GroundingChunkWeb{URI: "https://example.com/ai", Title: "AI Research"}}, // duplicate
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := client.extractTextFromResponse(response)
|
||||
if !strings.Contains(result, "## Sources") {
|
||||
t.Fatalf("expected sources section in result: %s", result)
|
||||
}
|
||||
if strings.Count(result, "- [") != 2 {
|
||||
t.Errorf("expected 2 unique citations, got %d", strings.Count(result, "- ["))
|
||||
}
|
||||
}
|
||||
|
||||
// Test convertMessages handles role mapping correctly
|
||||
func TestConvertMessagesRoles(t *testing.T) {
|
||||
client := &Client{}
|
||||
msgs := []*chat.ChatCompletionMessage{
|
||||
{Role: chat.ChatMessageRoleUser, Content: "user"},
|
||||
{Role: chat.ChatMessageRoleAssistant, Content: "assistant"},
|
||||
{Role: chat.ChatMessageRoleSystem, Content: "system"},
|
||||
}
|
||||
|
||||
contents := client.convertMessages(msgs)
|
||||
|
||||
expected := []string{"user", "model", "user"}
|
||||
|
||||
if len(contents) != len(expected) {
|
||||
t.Fatalf("expected %d contents, got %d", len(expected), len(contents))
|
||||
}
|
||||
|
||||
for i, c := range contents {
|
||||
if c.Role != expected[i] {
|
||||
t.Errorf("content %d expected role %s, got %s", i, expected[i], c.Role)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test isTTSModel method
|
||||
func TestIsTTSModel(t *testing.T) {
|
||||
client := &Client{}
|
||||
|
||||
tests := []struct {
|
||||
modelName string
|
||||
expected bool
|
||||
}{
|
||||
{"gemini-2.5-flash-preview-tts", true},
|
||||
{"text-to-speech-model", true},
|
||||
{"TTS-MODEL", true},
|
||||
{"gemini-pro", false},
|
||||
{"chat-bison", false},
|
||||
{"", false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := client.isTTSModel(test.modelName)
|
||||
if result != test.expected {
|
||||
t.Errorf("For model %v, expected %v, got %v", test.modelName, test.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test generateWAVFile method (basic test)
|
||||
func TestGenerateWAVFile(t *testing.T) {
|
||||
client := &Client{}
|
||||
|
||||
// Test with minimal PCM data
|
||||
pcmData := []byte{0x00, 0x01, 0x02, 0x03}
|
||||
|
||||
result, err := client.generateWAVFile(pcmData)
|
||||
if err != nil {
|
||||
t.Errorf("generateWAVFile failed: %v", err)
|
||||
}
|
||||
|
||||
// Check that we got some data back
|
||||
if len(result) == 0 {
|
||||
t.Error("generateWAVFile returned empty data")
|
||||
}
|
||||
|
||||
// Check that it starts with RIFF header
|
||||
if len(result) >= 4 && string(result[0:4]) != "RIFF" {
|
||||
t.Error("Generated WAV data doesn't start with RIFF header")
|
||||
}
|
||||
}
|
||||
|
||||
218
internal/plugins/ai/gemini/voices.go
Normal file
218
internal/plugins/ai/gemini/voices.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package gemini
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// GeminiVoice represents a Gemini TTS voice with its characteristics
|
||||
type GeminiVoice struct {
|
||||
Name string
|
||||
Description string
|
||||
Characteristics []string
|
||||
}
|
||||
|
||||
// GetGeminiVoices returns the current list of supported Gemini TTS voices
|
||||
// This list is maintained based on official Google Gemini documentation
|
||||
// https://ai.google.dev/gemini-api/docs/speech-generation
|
||||
func GetGeminiVoices() []GeminiVoice {
|
||||
return []GeminiVoice{
|
||||
// Firm voices
|
||||
{Name: "Kore", Description: "Firm and confident", Characteristics: []string{"firm", "confident", "default"}},
|
||||
{Name: "Orus", Description: "Firm and decisive", Characteristics: []string{"firm", "decisive"}},
|
||||
{Name: "Alnilam", Description: "Firm and strong", Characteristics: []string{"firm", "strong"}},
|
||||
|
||||
// Upbeat voices
|
||||
{Name: "Puck", Description: "Upbeat and energetic", Characteristics: []string{"upbeat", "energetic"}},
|
||||
{Name: "Laomedeia", Description: "Upbeat and lively", Characteristics: []string{"upbeat", "lively"}},
|
||||
|
||||
// Bright voices
|
||||
{Name: "Zephyr", Description: "Bright and cheerful", Characteristics: []string{"bright", "cheerful"}},
|
||||
{Name: "Autonoe", Description: "Bright and optimistic", Characteristics: []string{"bright", "optimistic"}},
|
||||
|
||||
// Informative voices
|
||||
{Name: "Charon", Description: "Informative and clear", Characteristics: []string{"informative", "clear"}},
|
||||
{Name: "Rasalgethi", Description: "Informative and professional", Characteristics: []string{"informative", "professional"}},
|
||||
|
||||
// Natural voices
|
||||
{Name: "Aoede", Description: "Breezy and natural", Characteristics: []string{"breezy", "natural"}},
|
||||
{Name: "Leda", Description: "Youthful and energetic", Characteristics: []string{"youthful", "energetic"}},
|
||||
|
||||
// Gentle voices
|
||||
{Name: "Vindemiatrix", Description: "Gentle and kind", Characteristics: []string{"gentle", "kind"}},
|
||||
{Name: "Achernar", Description: "Soft and gentle", Characteristics: []string{"soft", "gentle"}},
|
||||
{Name: "Enceladus", Description: "Breathy and soft", Characteristics: []string{"breathy", "soft"}},
|
||||
|
||||
// Warm voices
|
||||
{Name: "Sulafat", Description: "Warm and welcoming", Characteristics: []string{"warm", "welcoming"}},
|
||||
{Name: "Capella", Description: "Warm and approachable", Characteristics: []string{"warm", "approachable"}},
|
||||
|
||||
// Clear voices
|
||||
{Name: "Iapetus", Description: "Clear and articulate", Characteristics: []string{"clear", "articulate"}},
|
||||
{Name: "Erinome", Description: "Clear and precise", Characteristics: []string{"clear", "precise"}},
|
||||
|
||||
// Pleasant voices
|
||||
{Name: "Algieba", Description: "Smooth and pleasant", Characteristics: []string{"smooth", "pleasant"}},
|
||||
{Name: "Vega", Description: "Smooth and flowing", Characteristics: []string{"smooth", "flowing"}},
|
||||
|
||||
// Textured voices
|
||||
{Name: "Algenib", Description: "Gravelly texture", Characteristics: []string{"gravelly", "textured"}},
|
||||
|
||||
// Relaxed voices
|
||||
{Name: "Callirrhoe", Description: "Easy-going and relaxed", Characteristics: []string{"relaxed", "easy-going"}},
|
||||
{Name: "Despina", Description: "Calm and serene", Characteristics: []string{"calm", "serene"}},
|
||||
|
||||
// Mature voices
|
||||
{Name: "Gacrux", Description: "Mature and experienced", Characteristics: []string{"mature", "experienced"}},
|
||||
|
||||
// Expressive voices
|
||||
{Name: "Pulcherrima", Description: "Forward and expressive", Characteristics: []string{"forward", "expressive"}},
|
||||
{Name: "Lyra", Description: "Melodic and expressive", Characteristics: []string{"melodic", "expressive"}},
|
||||
|
||||
// Dynamic voices
|
||||
{Name: "Fenrir", Description: "Excitable and dynamic", Characteristics: []string{"excitable", "dynamic"}},
|
||||
{Name: "Sadachbia", Description: "Lively and animated", Characteristics: []string{"lively", "animated"}},
|
||||
|
||||
// Friendly voices
|
||||
{Name: "Achird", Description: "Friendly and approachable", Characteristics: []string{"friendly", "approachable"}},
|
||||
|
||||
// Casual voices
|
||||
{Name: "Zubenelgenubi", Description: "Casual and conversational", Characteristics: []string{"casual", "conversational"}},
|
||||
|
||||
// Additional voices from latest API
|
||||
{Name: "Sadaltager", Description: "Experimental voice with a calm and neutral tone", Characteristics: []string{"experimental", "calm", "neutral"}},
|
||||
{Name: "Schedar", Description: "Experimental voice with a warm and engaging tone", Characteristics: []string{"experimental", "warm", "engaging"}},
|
||||
{Name: "Umbriel", Description: "Experimental voice with a deep and resonant tone", Characteristics: []string{"experimental", "deep", "resonant"}},
|
||||
}
|
||||
}
|
||||
|
||||
// GetGeminiVoiceNames returns just the voice names in alphabetical order
|
||||
func GetGeminiVoiceNames() []string {
|
||||
voices := GetGeminiVoices()
|
||||
names := make([]string, len(voices))
|
||||
for i, voice := range voices {
|
||||
names[i] = voice.Name
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names
|
||||
}
|
||||
|
||||
// IsValidGeminiVoice checks if a voice name is valid
|
||||
func IsValidGeminiVoice(voiceName string) bool {
|
||||
if voiceName == "" {
|
||||
return true // Empty voice is valid (will use default)
|
||||
}
|
||||
|
||||
for _, voice := range GetGeminiVoices() {
|
||||
if voice.Name == voiceName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetGeminiVoiceByName returns a specific voice by name
|
||||
func GetGeminiVoiceByName(name string) (*GeminiVoice, error) {
|
||||
for _, voice := range GetGeminiVoices() {
|
||||
if voice.Name == name {
|
||||
return &voice, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("voice '%s' not found", name)
|
||||
}
|
||||
|
||||
// ListGeminiVoices formats the voice list for display
|
||||
func ListGeminiVoices(shellCompleteMode bool) string {
|
||||
if shellCompleteMode {
|
||||
// For shell completion, just return voice names
|
||||
names := GetGeminiVoiceNames()
|
||||
result := ""
|
||||
for _, name := range names {
|
||||
result += name + "\n"
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// For human-readable output
|
||||
voices := GetGeminiVoices()
|
||||
result := "Available Gemini Text-to-Speech voices:\n\n"
|
||||
|
||||
// Group by characteristics for better readability
|
||||
groups := map[string][]GeminiVoice{
|
||||
"Firm & Confident": {},
|
||||
"Bright & Cheerful": {},
|
||||
"Warm & Welcoming": {},
|
||||
"Clear & Professional": {},
|
||||
"Natural & Expressive": {},
|
||||
"Other Voices": {},
|
||||
}
|
||||
|
||||
for _, voice := range voices {
|
||||
placed := false
|
||||
for _, char := range voice.Characteristics {
|
||||
switch char {
|
||||
case "firm", "confident", "decisive", "strong":
|
||||
if !placed {
|
||||
groups["Firm & Confident"] = append(groups["Firm & Confident"], voice)
|
||||
placed = true
|
||||
}
|
||||
case "bright", "cheerful", "upbeat", "energetic", "lively":
|
||||
if !placed {
|
||||
groups["Bright & Cheerful"] = append(groups["Bright & Cheerful"], voice)
|
||||
placed = true
|
||||
}
|
||||
case "warm", "welcoming", "friendly", "approachable":
|
||||
if !placed {
|
||||
groups["Warm & Welcoming"] = append(groups["Warm & Welcoming"], voice)
|
||||
placed = true
|
||||
}
|
||||
case "clear", "informative", "professional", "articulate":
|
||||
if !placed {
|
||||
groups["Clear & Professional"] = append(groups["Clear & Professional"], voice)
|
||||
placed = true
|
||||
}
|
||||
case "natural", "expressive", "melodic", "breezy":
|
||||
if !placed {
|
||||
groups["Natural & Expressive"] = append(groups["Natural & Expressive"], voice)
|
||||
placed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !placed {
|
||||
groups["Other Voices"] = append(groups["Other Voices"], voice)
|
||||
}
|
||||
}
|
||||
|
||||
// Output grouped voices
|
||||
for groupName, groupVoices := range groups {
|
||||
if len(groupVoices) > 0 {
|
||||
result += fmt.Sprintf("%s:\n", groupName)
|
||||
for _, voice := range groupVoices {
|
||||
defaultStr := ""
|
||||
if voice.Name == "Kore" {
|
||||
defaultStr = " (default)"
|
||||
}
|
||||
result += fmt.Sprintf(" %-15s - %s%s\n", voice.Name, voice.Description, defaultStr)
|
||||
}
|
||||
result += "\n"
|
||||
}
|
||||
}
|
||||
|
||||
result += "Use --voice <voice_name> to select a specific voice.\n"
|
||||
result += "Example: fabric --voice Charon -m gemini-2.5-flash-preview-tts -o output.wav \"Hello world\"\n"
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// NOTE: This implementation maintains a curated list based on official Google documentation.
|
||||
// In the future, if Google provides a dynamic voice discovery API, this can be updated
|
||||
// to make API calls for real-time voice discovery.
|
||||
//
|
||||
// The current approach ensures:
|
||||
// 1. Fast response times (no API calls needed)
|
||||
// 2. Reliable voice information with descriptions
|
||||
// 3. Easy maintenance when new voices are added
|
||||
// 4. Offline functionality
|
||||
//
|
||||
// To update voices: Monitor Google's Gemini TTS documentation at:
|
||||
// https://ai.google.dev/gemini-api/docs/speech-generation
|
||||
@@ -1,6 +1,10 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/util"
|
||||
)
|
||||
|
||||
@@ -11,3 +15,40 @@ func NewVendorsModels() *VendorsModels {
|
||||
type VendorsModels struct {
|
||||
*util.GroupsItemsSelectorString
|
||||
}
|
||||
|
||||
// PrintWithVendor prints models including their vendor on each line.
|
||||
// When shellCompleteList is true, output is suitable for shell completion.
|
||||
// Default vendor and model are highlighted with an asterisk.
|
||||
func (o *VendorsModels) PrintWithVendor(shellCompleteList bool, defaultVendor, defaultModel string) {
|
||||
if !shellCompleteList {
|
||||
fmt.Printf("\n%v:\n", o.SelectionLabel)
|
||||
}
|
||||
|
||||
var currentItemIndex int
|
||||
|
||||
sortedGroups := make([]*util.GroupItems[string], len(o.GroupsItems))
|
||||
copy(sortedGroups, o.GroupsItems)
|
||||
sort.SliceStable(sortedGroups, func(i, j int) bool {
|
||||
return strings.ToLower(sortedGroups[i].Group) < strings.ToLower(sortedGroups[j].Group)
|
||||
})
|
||||
|
||||
for _, groupItems := range sortedGroups {
|
||||
items := make([]string, len(groupItems.Items))
|
||||
copy(items, groupItems.Items)
|
||||
sort.SliceStable(items, func(i, j int) bool {
|
||||
return strings.ToLower(items[i]) < strings.ToLower(items[j])
|
||||
})
|
||||
for _, item := range items {
|
||||
currentItemIndex++
|
||||
if shellCompleteList {
|
||||
fmt.Printf("%s|%s\n", groupItems.Group, item)
|
||||
} else {
|
||||
mark := " "
|
||||
if strings.EqualFold(groupItems.Group, defaultVendor) && strings.EqualFold(item, defaultModel) {
|
||||
mark = " *"
|
||||
}
|
||||
fmt.Printf("%s\t[%d]\t%s|%s\n", mark, currentItemIndex, groupItems.Group, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -31,3 +34,23 @@ func TestFindVendorsByModel(t *testing.T) {
|
||||
t.Fatalf("FindVendorsByModel() = %v, want %v", foundVendors, []string{"vendor1"})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintWithVendorMarksDefault(t *testing.T) {
|
||||
vendors := NewVendorsModels()
|
||||
vendors.AddGroupItems("vendor1", []string{"model1"}...)
|
||||
vendors.AddGroupItems("vendor2", []string{"model2"}...)
|
||||
|
||||
r, w, _ := os.Pipe()
|
||||
oldStdout := os.Stdout
|
||||
os.Stdout = w
|
||||
|
||||
vendors.PrintWithVendor(false, "vendor2", "model2")
|
||||
|
||||
w.Close()
|
||||
os.Stdout = oldStdout
|
||||
out, _ := io.ReadAll(r)
|
||||
|
||||
if !strings.Contains(string(out), " *\t[2]\tvendor2|model2") {
|
||||
t.Fatalf("default model not marked: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -61,6 +62,11 @@ func (t *transport_sec) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return t.underlyingTransport.RoundTrip(req)
|
||||
}
|
||||
|
||||
// IsConfigured returns true only if OLLAMA_API_URL environment variable is explicitly set
|
||||
func (o *Client) IsConfigured() bool {
|
||||
return os.Getenv("OLLAMA_API_URL") != ""
|
||||
}
|
||||
|
||||
func (o *Client) configure() (err error) {
|
||||
if o.apiUrl, err = url.Parse(o.ApiUrl.Value); err != nil {
|
||||
fmt.Printf("cannot parse URL: %s: %v\n", o.ApiUrl.Value, err)
|
||||
@@ -160,6 +166,7 @@ func (o *Client) NeedsRawMode(modelName string) bool {
|
||||
ollamaPrefixes := []string{
|
||||
"llama3",
|
||||
"llama2",
|
||||
"mistral",
|
||||
}
|
||||
for _, prefix := range ollamaPrefixes {
|
||||
if strings.HasPrefix(modelName, prefix) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user