mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-09 22:38:10 -05:00
Compare commits
213 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9abb410271 | ||
|
|
865820ece8 | ||
|
|
b7e47d510c | ||
|
|
61e72eb7fe | ||
|
|
90dbab6376 | ||
|
|
ef3c043f77 | ||
|
|
d65375da7b | ||
|
|
21186097e4 | ||
|
|
a97302d791 | ||
|
|
aaddc95ec0 | ||
|
|
1b7a1fa652 | ||
|
|
8a3d63ef48 | ||
|
|
609df943dd | ||
|
|
8941551f5a | ||
|
|
61f66f88e3 | ||
|
|
15de33107b | ||
|
|
81f9b1dabb | ||
|
|
888342c119 | ||
|
|
12d83dad6d | ||
|
|
14ef9fd41c | ||
|
|
584e0c8547 | ||
|
|
914b312c2e | ||
|
|
8153d690cc | ||
|
|
d03bdbeb9b | ||
|
|
87730043b5 | ||
|
|
3285b8e330 | ||
|
|
686d039392 | ||
|
|
d7683e4c39 | ||
|
|
56f995afb4 | ||
|
|
17bde814cc | ||
|
|
525f972d22 | ||
|
|
161ce65ae6 | ||
|
|
72f1429db9 | ||
|
|
b5fe3f2ad8 | ||
|
|
2155ff9fc0 | ||
|
|
d33175e5b6 | ||
|
|
6dbd24e541 | ||
|
|
d1c527c421 | ||
|
|
c0bd61ba49 | ||
|
|
8f0cc85742 | ||
|
|
7275dfbd6b | ||
|
|
9f94cfb718 | ||
|
|
e1fa674a3f | ||
|
|
34f804be3a | ||
|
|
83cd8a1912 | ||
|
|
d347ef0dcc | ||
|
|
44cc9bbcac | ||
|
|
4aa0b99c74 | ||
|
|
d90e8081ac | ||
|
|
70d43c5252 | ||
|
|
fd8216f1bd | ||
|
|
ae0c9009d9 | ||
|
|
4104317b34 | ||
|
|
401f0da689 | ||
|
|
6efe7960cd | ||
|
|
a6d63f4d0e | ||
|
|
8f3928f4b2 | ||
|
|
3380972df1 | ||
|
|
bad01040e3 | ||
|
|
0b26b930f9 | ||
|
|
5dbaf4f28f | ||
|
|
1d9cce5adf | ||
|
|
76622105e2 | ||
|
|
e6e3e34f53 | ||
|
|
7192ddbaa6 | ||
|
|
b5206d1923 | ||
|
|
81d60b4292 | ||
|
|
ca6660585d | ||
|
|
d70e7c570d | ||
|
|
4fb965ec9d | ||
|
|
a5544cb67c | ||
|
|
679a852c1c | ||
|
|
c685b4f810 | ||
|
|
9452d6bd2a | ||
|
|
a6eeff2c91 | ||
|
|
3eb314320e | ||
|
|
14d02073ab | ||
|
|
17a6020c2d | ||
|
|
e8d5d75286 | ||
|
|
eb6430295f | ||
|
|
c0adf73056 | ||
|
|
e373c5d5b8 | ||
|
|
0ed45d34cb | ||
|
|
6fadd428c0 | ||
|
|
87d2acbb43 | ||
|
|
3253cd284a | ||
|
|
bb72fe59d1 | ||
|
|
c88ef3d507 | ||
|
|
550a06c9ed | ||
|
|
3b7b9e07e7 | ||
|
|
bedcc08869 | ||
|
|
4b6f7ddf27 | ||
|
|
c96a4931d1 | ||
|
|
110eb37ffa | ||
|
|
fc3aa9c8d6 | ||
|
|
8e2c26d1a0 | ||
|
|
3766bab44a | ||
|
|
2eed7ec935 | ||
|
|
134f54d372 | ||
|
|
0ccf34ea8c | ||
|
|
7e9d7f6f32 | ||
|
|
d6d721737b | ||
|
|
2d102c1a02 | ||
|
|
6c185c25f6 | ||
|
|
b53005a6f1 | ||
|
|
e4d34f4937 | ||
|
|
689292f7f6 | ||
|
|
857b23c3fd | ||
|
|
42b5cb4413 | ||
|
|
3990fe013b | ||
|
|
81b4bf3756 | ||
|
|
ee1e7d399d | ||
|
|
859ef049d2 | ||
|
|
54825ec8d8 | ||
|
|
99e2e7da57 | ||
|
|
ed34ccfc73 | ||
|
|
c92bcc24da | ||
|
|
6ec0ea4f80 | ||
|
|
3c56d23b03 | ||
|
|
bcafea3b98 | ||
|
|
33df431993 | ||
|
|
d6551bed60 | ||
|
|
fe620ed278 | ||
|
|
7b8ea76239 | ||
|
|
9a55514bc0 | ||
|
|
5012621721 | ||
|
|
9e676fdcd8 | ||
|
|
e0489803f4 | ||
|
|
6c927e23b5 | ||
|
|
8fb7f19fb1 | ||
|
|
a0ba207db9 | ||
|
|
58f8d21ec0 | ||
|
|
2b87109e52 | ||
|
|
f54a052533 | ||
|
|
a67dd0555a | ||
|
|
19b568b075 | ||
|
|
5d3e0dae49 | ||
|
|
be37d889a0 | ||
|
|
be9046aee9 | ||
|
|
7e63a16f28 | ||
|
|
98292df3cc | ||
|
|
cc8711cc07 | ||
|
|
211c0fece0 | ||
|
|
7d6cb5604f | ||
|
|
f774c95865 | ||
|
|
8445b6aad7 | ||
|
|
2f881a2c06 | ||
|
|
6fc4d91a29 | ||
|
|
6126a14c9f | ||
|
|
9aa2e00b16 | ||
|
|
3a7e1cf527 | ||
|
|
913210d2a9 | ||
|
|
b2a2ea0c03 | ||
|
|
ad6260fab5 | ||
|
|
522c1038fb | ||
|
|
cab365f496 | ||
|
|
e714b6c5bf | ||
|
|
d8c3c29ff8 | ||
|
|
273ba3e943 | ||
|
|
70c5aacb45 | ||
|
|
c5db39a06d | ||
|
|
fa457e7812 | ||
|
|
dd6cd06b5a | ||
|
|
41b711f1ca | ||
|
|
8f7acac2b1 | ||
|
|
ea323c12ff | ||
|
|
dd83d7faca | ||
|
|
28937bb8ca | ||
|
|
a1c81c41cb | ||
|
|
24816f25d1 | ||
|
|
fcddedfe72 | ||
|
|
77c7323a39 | ||
|
|
7322d249e2 | ||
|
|
cfae9efcbb | ||
|
|
70aac4e5f9 | ||
|
|
4a34355c3b | ||
|
|
c51c4c8d3e | ||
|
|
a619c915e1 | ||
|
|
f4044cde7e | ||
|
|
a31af9fa80 | ||
|
|
a61590efeb | ||
|
|
26fccfe18e | ||
|
|
2b79a058de | ||
|
|
64da74e25a | ||
|
|
23cb9a9ee8 | ||
|
|
c2a15f6aa1 | ||
|
|
c293c6137b | ||
|
|
d2e2a6537e | ||
|
|
a6fc13dbdc | ||
|
|
4534ef6544 | ||
|
|
ebc0239339 | ||
|
|
bd33795f72 | ||
|
|
39aade44f6 | ||
|
|
3ae503c969 | ||
|
|
780fc42aa0 | ||
|
|
2967cfd1d4 | ||
|
|
0b28847e5d | ||
|
|
3a62c12791 | ||
|
|
75c1e84e08 | ||
|
|
147da29c1a | ||
|
|
329c843567 | ||
|
|
3bf1c95dc2 | ||
|
|
0bc7cd9785 | ||
|
|
680129e370 | ||
|
|
d574670084 | ||
|
|
5ad9943462 | ||
|
|
b96415d445 | ||
|
|
6f116ca527 | ||
|
|
bac7d87390 | ||
|
|
cf0b9d2c3d | ||
|
|
b2be94f2f8 | ||
|
|
9b4c20dd19 | ||
|
|
c7449c68b7 |
32
.github/workflows/release.yml
vendored
32
.github/workflows/release.yml
vendored
@@ -1,8 +1,9 @@
|
||||
name: Go Release
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [ tag_created ]
|
||||
push:
|
||||
branches: ["main"]
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
@@ -15,6 +16,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
@@ -40,6 +43,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
@@ -88,23 +93,36 @@ jobs:
|
||||
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
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release view ${{ github.ref_name }} || gh release create ${{ github.ref_name }} --title "Release ${{ github.ref_name }}" --notes "Automated release for ${{ github.ref_name }}"
|
||||
gh release view ${{ env.latest_tag }} || gh release create ${{ env.latest_tag }} --title "Release ${{ env.latest_tag }}" --notes "Automated release for ${{ env.latest_tag }}"
|
||||
|
||||
- name: Upload release artifact
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
|
||||
if: matrix.os == 'windows-latest'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release upload ${{ github.ref_name }} fabric-windows-${{ matrix.arch }}.exe
|
||||
gh release upload ${{ env.latest_tag }} fabric-windows-${{ matrix.arch }}.exe
|
||||
|
||||
- name: Upload release artifact
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && matrix.os != 'windows-latest'
|
||||
if: matrix.os != 'windows-latest'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release upload ${{ github.ref_name }} fabric-${OS}-${{ matrix.arch }}
|
||||
gh release upload ${{ env.latest_tag }} fabric-${OS}-${{ matrix.arch }}
|
||||
|
||||
81
.github/workflows/update-version-and-create-tag.yml
vendored
Normal file
81
.github/workflows/update-version-and-create-tag.yml
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
name: Update Version File and Create Tag
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main # Monitor the main branch
|
||||
|
||||
permissions:
|
||||
contents: write # Ensure the workflow has write permissions
|
||||
|
||||
jobs:
|
||||
update-version:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Get the latest tag
|
||||
id: get_latest_tag
|
||||
run: |
|
||||
latest_tag=$(git tag --sort=-creatordate | head -n 1)
|
||||
echo "Latest tag is: $latest_tag"
|
||||
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 }}
|
||||
major=$(echo "$latest_tag" | cut -d. -f1 | sed 's/v//')
|
||||
minor=$(echo "$latest_tag" | cut -d. -f2)
|
||||
patch=$(echo "$latest_tag" | cut -d. -f3)
|
||||
new_patch=$((patch + 1))
|
||||
new_tag="v${major}.${minor}.${new_patch}"
|
||||
echo "New tag is: $new_tag"
|
||||
echo "new_tag=$new_tag" >> $GITHUB_ENV # Save the new tag to environment file
|
||||
|
||||
- name: Update version.go file
|
||||
run: |
|
||||
echo "package main" > version.go
|
||||
echo "" >> version.go
|
||||
echo "var version = \"${{ env.new_tag }}\"" >> version.go
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git add version.go
|
||||
if ! git diff --staged --quiet; then
|
||||
git commit -m "Update version to ${{ env.new_tag }} and commit $commit_hash"
|
||||
else
|
||||
echo "No changes to commit."
|
||||
fi
|
||||
|
||||
- name: Push changes
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Use GITHUB_TOKEN to authenticate the push
|
||||
run: |
|
||||
git push origin main # Push changes to the main branch
|
||||
|
||||
- name: Create a new tag
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.TAG_PAT }}
|
||||
run: |
|
||||
git tag ${{ env.new_tag }}
|
||||
git push origin ${{ env.new_tag }} # Push the new tag
|
||||
|
||||
- name: Dispatch event to trigger release workflow
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Use GITHUB_TOKEN to authenticate the dispatch
|
||||
run: |
|
||||
curl -X POST \
|
||||
-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 }}"}}'
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,6 +11,7 @@ __pycache__/
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
.idea
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
|
||||
312
Alma.md
Normal file
312
Alma.md
Normal file
@@ -0,0 +1,312 @@
|
||||
## Document Purpose
|
||||
|
||||
This document captures the SPQA policy and State for Alma Security, a security startup out of Redwood City, Ca.
|
||||
|
||||
This is part of the SPQA context that will be used to answer questions and create artifacts for the company, e.g., company strategy, security strategy, quarterly security reports (QSRs), project plans, recommendations on which projects to undertake, which investments to take and avoid, and other such decisions.
|
||||
|
||||
A major aspect of the SPQA system is the definition of the company's mission, goals, KPIs, and challenges. These shape everything within the company and thus should be used to shape the recommendations made when asked.
|
||||
|
||||
In addition to the clearly stated goals and other defining characteristics listed above, there will also be a streaming list of updates coming into this system using the Activity document.
|
||||
|
||||
Those will be changes, updates, or modifications to the direction of the company. For example, if Goal number 4 is to build a new datacenter in Boise, Idaho, but we see an update in the Activity section that says we've lost the ability to build in Boise, we should consider goal #4 out of the picture for prioritization and other decision purposes. In other words, the streaming activity log into this document should be considered updates to the core content.
|
||||
|
||||
## Company History
|
||||
|
||||
Alma Security was started by Chris Meyers, who was previously at Sigma Systems as CTO and HPE as a senior security engineer.
|
||||
|
||||
He started the company becuase, "I saw a gap in the authentication market, where companies were only looking at one or two aspects of one's identity to do authentication. They we're looking at the whole picture and turning that into a continuous authentication story."
|
||||
|
||||
## Company Mission
|
||||
|
||||
The mission of Alma Security is to ensure businesses can continuously authenticate their users using their whole selves.
|
||||
|
||||
## Company Goals (G1 means goal 1, G2 is goal 2, etc. Treat each item (goal/kpi/etc) as half as important as the one before it.)
|
||||
|
||||
NOTE: Some goals are things like project rollouts which serve the higher goals. In that case they shouldn't always be considered so much lower priority because one is serving the other.
|
||||
|
||||
## Company Goals
|
||||
|
||||
- G1: Achieve 20% market share by January 2025
|
||||
- G2: Hit 10000 active customers by January 2025
|
||||
- G3: Hit a customer trust score of 90+% by January 2025
|
||||
- G4: Get churn below 5% by August 2024
|
||||
- G5: Launch in Europe by August 2024
|
||||
- G6: Launch in India by November 2024
|
||||
- G7: Launch Mood-monitor integration by February 2024
|
||||
- G8: Launch partnership with Apple Passkeys by June 2024
|
||||
|
||||
## Company KPIs
|
||||
|
||||
- K1: Current marketshare percentage
|
||||
- K2: Number of active customers
|
||||
- K3: Current churn percentage
|
||||
- K4: Launched_in_Europe (yes/no)
|
||||
- K4: Launched_in_India (yes/no)
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
## Security Team Mission
|
||||
|
||||
- SM1: Protect Alma Security's customers and intellectual property from security and privacy incidents.
|
||||
|
||||
## Security Team Goals
|
||||
|
||||
- SG1: Secure all customer data -- especially biometric -- from security and privacy incidents.
|
||||
- SG2: Protect Alma Security's intellectual property from being captured by unathorized parties.
|
||||
- SG3: Reach a time to detect malicious behavior of less than 4 minutes by January 2025
|
||||
- SG4: Ensure the public trusts our product, because it's an authentication product we can't survive if people don't trust us.
|
||||
- SG5: Reach a time to remediate critical vulnerabilties on crown jewel systems of less than 16 hours by August 2025
|
||||
- SG6: Reach a time to remediate critical vulnerabilties on all systems of less than 3 days by August 2025
|
||||
- SG7: Complete audit of Apple Passkey integration by February 2025
|
||||
- SG8: Complete remediation of Apple Passkey vulns by February 2025
|
||||
|
||||
## Security Team KPIs (How we measure the team)
|
||||
|
||||
- SK1: TTD: Time to detect malicious behavior (Minutes)
|
||||
- SK1: TTI: Time to begin investigation of malicious behavior (Minutes)
|
||||
- SK3: TTR-CJC: Time to remediate critical vulnerabilities on crown jewel systems (Hours)
|
||||
- SK3: TTR-C: Time to remediate critical vulnerabilities on all systems (Hours)
|
||||
- SK4: PT: Public trust score (Complete, Significant, Moderate, Minimal, Distrust, N/A)
|
||||
|
||||
## Risk Register (The things we're most worried about)
|
||||
|
||||
- R1: Our infrastructure security team is understaffed by 50% after 5 key people left
|
||||
- R2: We are not currently monitoring our external perimeter for attack surface related vulnerabilities like open ports, listening applications, unknown hosts, unknown subdomains pointing to these things, etc. We only do scans once every couple of months and we don't really have anyone to look at the results
|
||||
- R3: It takes us multiple days to investigate potential malicious behavior on our systems.
|
||||
- R4: We lack a full list of our assets, including externally facing hosts, S3 buckets, etc., which make up our attack surface
|
||||
- R5: We have a low public trust score due to the events of 2022.
|
||||
|
||||
## Security Team Narrative
|
||||
|
||||
### Background
|
||||
|
||||
Alma hired a new security team starting in January of 2023 and we have been building out the program since then. The philosophy and approach for the security team is to explicitly articulate what we believe the highest risks are to Alma, to deploy targeted strategies to address those risks, and to use clear, transparent KPIs to show progress towards our goals over time.
|
||||
|
||||
### Current Risks
|
||||
|
||||
So our risk register looks like this:
|
||||
|
||||
1. We are understaffed by 50% after 5 key people left in 2022
|
||||
2. Our perimeter is not being monitored for attack surface related vulnerabilities
|
||||
3. It takes us too long to detect and start investigating malicious behavior on our systems
|
||||
4. We do not have a full list of our assets, which makes it difficult to know what we need to protect
|
||||
5. We have a low public trust score due to the events of 2022
|
||||
|
||||
### Strategies
|
||||
|
||||
As such, our strategies are as follows:
|
||||
|
||||
1. Hire 5 more A-tier security professionals
|
||||
2. Purchase and implement an attack surface management solution
|
||||
3. Invest in our detection and response capabilities
|
||||
4. Purchase an asset inventory system that integrates with our attack surface management tool
|
||||
5. Leverage PR to share as much of our progress as possible with the public to rebuild trust
|
||||
|
||||
### How We're Doing
|
||||
|
||||
We believe being transparent about our progress is key to everything, and for that reason we maintain a limited number of KPIs that we update every quarter. These metrics will not change often. They will remain consistent so that it's easy to track how we're spending our resources and the progress we're making.
|
||||
|
||||
Those KPIs are:
|
||||
|
||||
1. Time to detect malicious behavior
|
||||
2. Time to start investigating malicious behavior
|
||||
3. Time to remediate critical vulnerabilities on crown jewel systems
|
||||
4. Time to remediate critical vulnerabilities on all systems
|
||||
5. Our public trust score
|
||||
|
||||
As of $DATE$, our KPIs for these are currently:
|
||||
|
||||
$GIVE CURRENT KPIs from the Activity section below$
|
||||
|
||||
$INSERT GRAPHS OF KPI PROGRESS OVER TIME HERE$
|
||||
|
||||
## Security Team Strategies
|
||||
|
||||
- STS1: Hire 5 more A-tier security professionals
|
||||
- STS2: Purchase an attack surface management solution
|
||||
- STS3: Invest in our detection and response capabilities
|
||||
- STS4: Purchase an asset inventory system that integrates with our attack surface management tool
|
||||
|
||||
## Infrastructure Notes (a basic description of our tech stack and various context around it)
|
||||
|
||||
- We currenty have no WAF protecting our main web app but we're considering adding one
|
||||
- We have had some issues with S3 buckets becoming public, or being set up as public, which has lead to some close calls with customer data almost being exposed.
|
||||
- alma.amazon-domain.com is our primary S3 bucket that contains everything, but it's not public readable or listable
|
||||
- We have a root account for our AWS account that doesn't yet have 2FA on it, but we're working on fixing that within a few weeks (but it's been open for a few months)
|
||||
- We also use Postgres for all our databases.
|
||||
- Developers have root access to the all kubernetes nodes via SSH on port 45,001 using a shared developer key issued during laptop provisioning.
|
||||
- We're a kubernetes shop and do everything through AWS
|
||||
- We're logging most stuff to Cloudtrail and we kind of use guarduty, but we don't have a 24/7 team to monitor alerts and logs. We should add that to our list of challenges next time we update our overarll policy
|
||||
- We also have a Windows infrastructure because some key personell came from Microsoft. The DC is hosted in our head office which is in Redwood City, and anyone who works in that office (most of the 300 employees) uses that to log in when tehy start work. The domain is ALMA.
|
||||
- There's a domain-joined fileserver running Windows 2012 that most people use to upload new ideas and plans for new products. It uses Windows authentication from the domain.
|
||||
- We use a palo alto firewall with 2fa using windows authenticator tied to SSO.
|
||||
- The name of the AI system doing all this context creation using SPQA is Alma, which is also the name of the company.
|
||||
- We use Workday for HR stuff. Slack for realtime communications. Outlook 365 as a service. Sentinel One on the workstations and laptops. Servers in AWS are mostly Amazon Linux 2 with a few Ubuntu boxes that are a few years old.
|
||||
- We also primarily use Postgres for all of our systems.
|
||||
|
||||
## Team
|
||||
|
||||
TEAM MEMBER | TEAM ASSIGNED | SKILLS | PAY LEVEL | LOCATION | PROJECTS
|
||||
|
||||
Nadia Khan | Detection and Response | D&R (Expert), AWS (Strong), Python (Expert), Kubernetes (Basic), Postgres (Basic) | $249K | Redwood City
|
||||
Chris Magann | Vulnerability Management | VM (Expert), AWS (Strong), Python (Basic), Postgres (Basic) | $212K | Redwood City
|
||||
Tigan Wang | Vulnerability Management | VM (Expert), AWS (Strong), Python (Basic), Postgres (Basic) | $217K | Redwood City
|
||||
|
||||
## Projects
|
||||
|
||||
PROJECT NAME | PROJECT DESCRIPTION | PROJECT PRIORITY | PROJECT MEMBERS | START DATE | END DATE | STATUS | PROJECT COST
|
||||
|
||||
WAF Install | Install a WAF in front of our main web app | Critical | Nadia Khan | 2024-01-01 - Ongoing | In Progress | $112K one-time, $9K/month
|
||||
|
||||
Multi-Factor Authentication (MFA) Rollout | Implement MFA across all internal and external systems | Critical | Chris Magaan | 2024-01-15 | 2024-05-01 | Planned | $80K one-time, $5K/month
|
||||
|
||||
Procure and Implement ASM | Implement continuous monitoring for attack surface vulnerabilities | High | Tigan Wang | 2024-02-15 | 2024-06-15 | Not Started | $75K one-time, $6K/month
|
||||
|
||||
Data Encryption Upgrade | Upgrade encryption protocols for all sensitive data | Medium | Nadia Khan | 2024-04-01 | 2024-08-01 | Planned | $95K one-time
|
||||
|
||||
Incident Response Enhancement | Develop and implement a 24/7 incident response team | High | Nadia Khan | 2024-03-01 | 2024-07-01 | In Progress | $150K one-time, $10K/month
|
||||
|
||||
Cloud Security Optimization | Optimize AWS cloud security configurations and practices | Medium | Tigan Wang | 2024-02-01 | 2024-06-01 | In Progress | $100K one-time, $8K/month
|
||||
|
||||
S3 Bucket Security | Review and secure all S3 buckets to prevent data breaches | High | Chris Magaan | 2024-01-10 | 2024-04-10 | In Progress | $70K one-time, $5K/month
|
||||
|
||||
SQL Injection Mitigation | Implement measures to eliminate SQL injection vulnerabilities | High | Tigan Wang | 2024-01-20 | 2024-05-20 | Not Started | $60K one-time
|
||||
|
||||
## SECURITY POSTURE (To be referenced for compliance questions and security questionnaires)
|
||||
|
||||
July 2019
|
||||
Admin accounts still not required to use 2FA.
|
||||
Company laptops distributed to employees, no MDM yet for device management.
|
||||
AWS IAM roles created for engineers, but root access still frequently used.
|
||||
Started basic vulnerability scanning using open-source tools.
|
||||
December 2019
|
||||
|
||||
MFA enforced for all Google Workspace accounts after a phishing attempt.
|
||||
Introduced ClamAV for basic endpoint protection on corporate laptops.
|
||||
AWS GuardDuty enabled for threat detection, but no formal incident response team.
|
||||
First incident response plan table-top exercise conducted, but findings not fully documented.
|
||||
April 2020
|
||||
|
||||
Migrated from Google Workspace to Office 365, with MFA enabled for all users.
|
||||
Rolled out SentinelOne for endpoint protection on 50% of company laptops.
|
||||
Implemented least-privilege access control for AWS IAM roles.
|
||||
First formal vendor risk management review completed for major SaaS providers.
|
||||
August 2020
|
||||
|
||||
Completed full deployment of SentinelOne across all endpoints.
|
||||
Implemented AWS CloudWatch for real-time alerts; however, logs still not monitored 24/7.
|
||||
Began encrypting all AWS S3 buckets at rest using server-side encryption.
|
||||
First internal review of data retention policies, started drafting data disposal policy.
|
||||
January 2021
|
||||
|
||||
Rolled out Jamf MDM for centralized management of macOS devices, enforcing encryption (FileVault) on all laptops.
|
||||
Strengthened Office 365 security by implementing phishing-resistant MFA using authenticator apps.
|
||||
AWS KMS introduced for managing encryption keys; manual key rotation policy documented.
|
||||
Introduced formal onboarding and offboarding processes for employee account management.
|
||||
July 2021
|
||||
|
||||
Conditional access policies introduced for Office 365, restricting access based on geography (US-only).
|
||||
Conducted company-wide security awareness training for the first time, focusing on phishing threats.
|
||||
Completed first backup and disaster recovery (DR) drill with AWS, documenting recovery times.
|
||||
AWS Config deployed to monitor and enforce encryption and access control policies across accounts.
|
||||
December 2021
|
||||
|
||||
Full migration to AWS for all production systems completed.
|
||||
Incident response playbook finalized and shared with the security team; still no 24/7 monitoring.
|
||||
Documented data classification policies for handling sensitive customer data in preparation for SOC 2 audit.
|
||||
First third-party penetration test conducted, critical vulnerabilities identified and remediated within 30 days.
|
||||
March 2022
|
||||
|
||||
Rolled out company-wide 2FA for all critical systems, including Office 365, AWS, GitHub, and Slack.
|
||||
Introduced AWS Secrets Manager for managing sensitive credentials, eliminating hardcoded API keys.
|
||||
Updated all documentation for identity and access management in preparation for SOC 2 Type 1 audit.
|
||||
First external vulnerability scan completed using Qualys, with remediation SLAs established.
|
||||
April 2022
|
||||
|
||||
Updated and consolidated all security policies (incident response, access control, data retention) in preparation for SOC 2 audit.
|
||||
Conducted tabletop exercise for ransomware response, documenting gaps in the incident response process.
|
||||
Implemented Just-In-Time (JIT) access for administrative privileges in AWS, reducing unnecessary persistent access.
|
||||
October 2022
|
||||
|
||||
Passed SOC 2 Type 1 audit, with recommendations to improve monitoring and asset management.
|
||||
Launched quarterly phishing simulations to raise employee awareness and track training effectiveness.
|
||||
Fully enforced encryption for all customer data in transit and at rest using AWS KMS.
|
||||
Extended GuardDuty to cover all AWS regions; started monitoring alerts daily.
|
||||
January 2023
|
||||
|
||||
Hired a dedicated CISO and expanded security team by 30%.
|
||||
Integrated continuous vulnerability scanning across all externally facing assets using Qualys.
|
||||
Conducted first third-party vendor risk assessment to ensure alignment with SOC 2 and internal security standards.
|
||||
Implemented automated patch management for all AWS EC2 instances, reducing time to deploy critical patches.
|
||||
July 2023
|
||||
|
||||
Rolled out continuous attack surface monitoring (ASM) to identify and remediate external vulnerabilities.
|
||||
Performed annual data retention review, ensuring compliance with SOC 2 and GDPR requirements.
|
||||
Conducted a disaster recovery drill for AWS workloads, achieving a recovery time objective (RTO) of under 4 hours.
|
||||
Completed SOC 2 Type 2 readiness assessment, with focus on improving incident response times.
|
||||
November 2023
|
||||
|
||||
Updated incident response documentation and assigned 24/7 monitoring to a third-party SOC provider.
|
||||
Rolled out zero-trust network architecture across the organization, removing reliance on VPN for remote access.
|
||||
Passed SOC 2 Type 2 audit with no major findings; recommendations included improved asset inventory tracking.
|
||||
Conducted full audit of access control policies and JIT access implementation in preparation for ISO 27001 certification.
|
||||
April 2024
|
||||
|
||||
Implemented AI-driven threat detection to reduce time to detect security incidents from 10 hours to under 2 hours.
|
||||
Completed full encryption audit across all databases, ensuring compliance with GDPR, HIPAA, and other privacy regulations.
|
||||
Updated employee training programs to include privacy regulations (GDPR, CCPA) and data handling best practices.
|
||||
Completed internal review and audit of vendor access to critical systems as part of SOC 2 compliance effort.
|
||||
Completed move of all AWS services to us-west-2 and us-east-1 regions for 100% us-based cloud services.
|
||||
October 2024
|
||||
|
||||
Conducted organization-wide review of data retention and disposal policies, implementing automated data deletion for expired data.
|
||||
Implemented continuous compliance monitoring for SOC 2, with automated alerts for deviations in access controls and encryption settings.
|
||||
Finalized implementation of AI-based monitoring and response systems, significantly reducing time to remediate critical vulnerabilities.
|
||||
Passed SOC 2 Type 2 and ISO 27001 audits with zero non-conformities, achieving full compliance across all control areas.March 2018
|
||||
|
||||
Personal Gmail accounts used for internal and external communication.
|
||||
No 2FA enabled on any accounts.
|
||||
AWS accounts shared with engineers, no IAM roles or formal access control policies.
|
||||
No centralized endpoint protection; employees use personal laptops with no security controls.
|
||||
No documented security policies or incident response plan.
|
||||
September 2018
|
||||
|
||||
Initiated migration from personal Gmail to Google Workspace (G Suite) for business email.
|
||||
Password complexity requirements introduced (minimum 8 characters).
|
||||
AWS root credentials still shared among team members, no MFA enabled.
|
||||
No formal logging or monitoring in place for AWS activity.
|
||||
February 2019
|
||||
|
||||
Completed migration to Google Workspace; no email encryption yet.
|
||||
Introduced a basic password manager (LastPass) but no enforcement policy.
|
||||
AWS CloudTrail enabled for logging, but no one is reviewing logs.
|
||||
First draft of the incident response plan created, but not tested.
|
||||
June 2019
|
||||
|
||||
Enforced MFA for Google Workspace admin accounts; standard user
|
||||
## CURRENT STATE (KPIs, Metrics, Project Activity Updates, etc.)
|
||||
- October 2022: Current time to detect malicious behavior is 81 hours
|
||||
- October 2022: Current time to start investigating malicious behavior is 82 hours
|
||||
- October 2022: Current time to remediate critical vulnerabilities on crown jewel systems is 21 days
|
||||
- October 2022: Current time to remediate critical vulnerabilities on all systems is 51 days
|
||||
- January 2023: Current time to detect malicious behavior is 62 hours
|
||||
- January 2023: Current time to start investigating malicious behavior is 72 hours
|
||||
- January 2023: Current time to remediate critical vulnerabilities on crown jewel systems is 17 days
|
||||
- January 2023: Current time to remediate critical vulnerabilities on all systems is 43 days
|
||||
- July 2023: Current time to detect malicious behavior is 29 hours
|
||||
- July 2023: Current time to start investigating malicious behavior is 41 hours
|
||||
- July 2023: Current time to remediate critical vulnerabilities on crown jewel systems is 12 days
|
||||
- July 2023: Current time to remediate critical vulnerabilities on all systems is 29 days
|
||||
- November 2023: Current time to start detect malicious behavior is 12 hours
|
||||
- November 2023: Current time to start investigating malicious behavior is 16 hours
|
||||
- November 2023: Current time to remediate critical vulnerabilities on crown jewel systems is 9 days
|
||||
- November 2023: Current time to remediate critical vulnerabilities on all systems is 17 days
|
||||
- February 2024: Started attack surface management vendor selection process
|
||||
- January 2024: Current time to start detect malicious behavior is 9 hours
|
||||
- January 2024: Current time to start investigating malicious behavior is 14 hours
|
||||
- January 2024: Current time to remediate critical vulnerabilities on crown jewel systems is 8 days
|
||||
- January 2024: Current time to remediate critical vulnerabilities on all systems is 12 days
|
||||
- March 2024: We're now remediating crits on crown jewels in less than 6 days
|
||||
- April 2024: We're now remediating all criticals within 11 days
|
||||
- July 2024: Criticals are now being fixed in 9 days
|
||||
- On August 5 we got remediation of critical vulnerabilities down to 7 days
|
||||
2
NOTES.md
2
NOTES.md
@@ -2,7 +2,7 @@
|
||||
|
||||
- The goal is to bring more encapsulation of the models management and simplified configuration management to bring increased flexibility, transparency on the overall flow, and simplicity in adding new model.
|
||||
- We need to differentiate:
|
||||
- Vendors: the producer of models (like OpenAI, Anthropric, Ollama, ..etc) and their associated APIs
|
||||
- Vendors: the producer of models (like OpenAI, Azure, Anthropric, Ollama, ..etc) and their associated APIs
|
||||
- Models: the LLM models these vendors are making public
|
||||
- Each vendor and operations allowed by the vendor needs to be encapsulated. This includes:
|
||||
- The questions needed to setup the model (like the API key, or the URL)
|
||||
|
||||
166
README.md
166
README.md
@@ -14,6 +14,7 @@
|
||||
<h4><code>fabric</code> is an open-source framework for augmenting humans using AI.</h4>
|
||||
</p>
|
||||
|
||||
[Updates](#updates) •
|
||||
[What and Why](#whatandwhy) •
|
||||
[Philosophy](#philosophy) •
|
||||
[Installation](#Installation) •
|
||||
@@ -28,6 +29,7 @@
|
||||
|
||||
## Navigation
|
||||
|
||||
- [Updates](#updates)
|
||||
- [What and Why](#what-and-why)
|
||||
- [Philosophy](#philosophy)
|
||||
- [Breaking problems into components](#breaking-problems-into-components)
|
||||
@@ -41,21 +43,24 @@
|
||||
- [Just use the Patterns](#just-use-the-patterns)
|
||||
- [Custom Patterns](#custom-patterns)
|
||||
- [Helper Apps](#helper-apps)
|
||||
- [pbpaste](#pbpaste)
|
||||
- [Meta](#meta)
|
||||
- [Primary contributors](#primary-contributors)
|
||||
|
||||
<br />
|
||||
|
||||
> [!NOTE]
|
||||
August 20, 2024 — We have migrated to Go, and the transition has been pretty smooth! The biggest thing to know is that **the previous installation instructions in the various Fabric videos out there will no longer work** because they were for the legacy (Python) version. Check the new [install instructions](#Installation) below.
|
||||
>
|
||||
>
|
||||
> **The following command line options were changed during the migration to Go:**
|
||||
## Updates
|
||||
|
||||
> [!NOTE]
|
||||
September 15, 2024 — Lots of new stuff!
|
||||
> * Fabric now supports calling the new `o1-preview` model using the `-r` switch (which stands for raw. Normal queries won't work with `o1-preview` because they disabled System access and don't allow us to set `Temperature`.
|
||||
> * We have early support for Raycast! Under the `/patterns` directory there's a `raycast` directory with scripts that can be called from Raycast. If you add a scripts directory within Raycast and point it to your `~/.config/fabric/patterns/raycast` directory, you'll then be able to 1) invoke Raycast, type the name of the script, and then 2) paste in the content to be passed, and the results will return in Raycast. There's currently only one script in there but I am (Daniel) adding more.
|
||||
> * **Go Migration: The following command line options were changed during the migration to Go:**
|
||||
> * You now need to use the -c option instead of -C to copy the result to the clipboard.
|
||||
> * You now need to use the -s option instead of -S to stream results in realtime.
|
||||
> * The following command line options have been removed --agents (-a), --gui, --clearsession, --remoteOllamaServer, and --sessionlog options
|
||||
> * You can now use --Setup (-S) to configure an Ollama server.
|
||||
> * **Please be patient while our developers rewrite the gui in go**
|
||||
> * The following command line options have been removed `--agents` (-a), `--gui`, `--clearsession`, `--remoteOllamaServer`, and `--sessionlog`
|
||||
> * You can now use (-S) to configure an Ollama server.
|
||||
> * **We're working on a GUI rewrite in Go as well**
|
||||
|
||||
## Intro videos
|
||||
|
||||
@@ -109,6 +114,29 @@ 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.
|
||||
|
||||
### Get Latest Release Binaries
|
||||
|
||||
```bash
|
||||
# Windows:
|
||||
curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-windows-amd64.exe > fabric.exe && fabric.exe --version
|
||||
|
||||
# MacOS (arm64):
|
||||
curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-darwin-arm64 > fabric && chmod +x fabric && ./fabric --version
|
||||
|
||||
# MacOS (amd64):
|
||||
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
|
||||
```
|
||||
|
||||
### From Source
|
||||
|
||||
To install Fabric, [make sure Go is installed](https://go.dev/doc/install), and then run the following command.
|
||||
|
||||
```bash
|
||||
@@ -133,9 +161,9 @@ export PATH=$GOPATH/bin:$GOROOT/bin:$HOME/.local/bin:$PATH
|
||||
for Apple Silicon based macs
|
||||
```bash
|
||||
# Golang environment variables
|
||||
export GOROOT=/opt/homebrew/bin/go
|
||||
export GOROOT=$(brew --prefix go)/libexec
|
||||
export GOPATH=$HOME/go
|
||||
export PATH=$GOPATH/bin:$GOROOT/bin:$HOME/.local/bin:$PATH:
|
||||
export PATH=$GOPATH/bin:$GOROOT/bin:$HOME/.local/bin:$PATH
|
||||
```
|
||||
|
||||
### Setup
|
||||
@@ -169,7 +197,7 @@ Then [set your environmental variables](#environmental-variables) as shown above
|
||||
|
||||
The great thing about Go is that it's super easy to upgrade. Just run the same command you used to install it in the first place and you'll always get the latest version.
|
||||
```bash
|
||||
go install github.com/danielmiessler/fabric@latest
|
||||
go install -ldflags "-X main.version=$(git describe --tags --always)" github.com/danielmiessler/fabric@latest
|
||||
```
|
||||
|
||||
## Usage
|
||||
@@ -180,39 +208,50 @@ fabric -h
|
||||
```
|
||||
|
||||
```bash
|
||||
usage: fabric -h
|
||||
|
||||
Usage:
|
||||
fabric [OPTIONS]
|
||||
|
||||
Application Options:
|
||||
-p, --pattern= Choose a pattern
|
||||
-v, --variable= Values for pattern variables, e.g. -v=$name:John -v=$age:30
|
||||
-C, --context= Choose a context
|
||||
--session= Choose a session
|
||||
-S, --setup Run setup
|
||||
--setup-skip-update-patterns Skip update patterns at setup
|
||||
-t, --temperature= Set temperature (default: 0.7)
|
||||
-T, --topp= Set top P (default: 0.9)
|
||||
-s, --stream Stream
|
||||
-P, --presencepenalty= Set presence penalty (default: 0.0)
|
||||
-F, --frequencypenalty= Set frequency penalty (default: 0.0)
|
||||
-l, --listpatterns List all patterns
|
||||
-L, --listmodels List all available models
|
||||
-x, --listcontexts List all contexts
|
||||
-X, --listsessions List all sessions
|
||||
-U, --updatepatterns Update patterns
|
||||
-c, --copy Copy to clipboard
|
||||
-m, --model= Choose model
|
||||
-o, --output= Output to file
|
||||
-n, --latest= Number of latest patterns to list (default: 0)
|
||||
-d, --changeDefaultModel Change default pattern
|
||||
-y, --youtube= YouTube video url to grab transcript, comments from it and send to chat
|
||||
--transcript Grab transcript from YouTube video and send to chat
|
||||
--comments Grab comments from YouTube video and send to chat
|
||||
--dry-run Show what would be sent to the model without actually sending it
|
||||
-p, --pattern= Choose a pattern from the available patterns
|
||||
-v, --variable= Values for pattern variables, e.g. -v=#role:expert -v=#points:30"
|
||||
-C, --context= Choose a context from the available contexts
|
||||
--session= Choose a session from the available sessions
|
||||
-S, --setup Run setup for all reconfigurable parts of fabric
|
||||
-t, --temperature= Set temperature (default: 0.7)
|
||||
-T, --topp= Set top P (default: 0.9)
|
||||
-s, --stream Stream
|
||||
-P, --presencepenalty= Set presence penalty (default: 0.0)
|
||||
-r, --raw 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.
|
||||
-F, --frequencypenalty= Set frequency penalty (default: 0.0)
|
||||
-l, --listpatterns List all patterns
|
||||
-L, --listmodels List all available models
|
||||
-x, --listcontexts List all contexts
|
||||
-X, --listsessions List all sessions
|
||||
-U, --updatepatterns Update patterns
|
||||
-c, --copy Copy to clipboard
|
||||
-m, --model= Choose model
|
||||
-o, --output= Output to file
|
||||
--output-session Output the entire session (also a temporary one) to the output file
|
||||
-n, --latest= Number of latest patterns to list (default: 0)
|
||||
-d, --changeDefaultModel Change default model
|
||||
-y, --youtube= YouTube video "URL" to grab transcript, comments from it and send to chat
|
||||
--transcript Grab transcript from YouTube video and send to chat (it used per default).
|
||||
--comments Grab comments from YouTube video and send to chat
|
||||
-g, --language= Specify the Language Code for the chat, e.g. -g=en -g=zh
|
||||
-u, --scrape_url= Scrape website URL to markdown using Jina AI
|
||||
-q, --scrape_question= Search question using Jina AI
|
||||
-e, --seed= Seed to be used for LMM generation
|
||||
-w, --wipecontext= Wipe context
|
||||
-W, --wipesession= Wipe session
|
||||
--printcontext= Print context
|
||||
--printsession= Print session
|
||||
--readability Convert HTML input into a clean, readable view
|
||||
--dry-run Show what would be sent to the model without actually sending it
|
||||
--version Print current version
|
||||
|
||||
Help Options:
|
||||
-h, --help Show this help message
|
||||
-h, --help Show this help message
|
||||
|
||||
```
|
||||
|
||||
@@ -236,6 +275,8 @@ https://github.com/danielmiessler/fabric/blob/main/patterns/extract_wisdom/syste
|
||||
|
||||
## Examples
|
||||
|
||||
> The following examples use the macOS `pbpaste` to paste from the clipboard. See the [pbpaste](#pbpaste) section below for Windows and Linux alternatives.
|
||||
|
||||
Now let's look at some things you can do with Fabric.
|
||||
|
||||
1. Run the `summarize` Pattern based on input from `stdin`. In this case, the body of an article.
|
||||
@@ -253,7 +294,7 @@ pbpaste | fabric --stream --pattern analyze_claims
|
||||
3. 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).
|
||||
|
||||
```bash
|
||||
yt --transcript https://youtube.com/watch?v=uXs-zPc63kM | fabric --stream --pattern extract_wisdom
|
||||
fabric -y "https://youtube.com/watch?v=uXs-zPc63kM" --stream --pattern extract_wisdom
|
||||
```
|
||||
|
||||
4. Create patterns- you must create a .md file with the pattern and save it to ~/.config/fabric/patterns/[yourpatternname].
|
||||
@@ -277,7 +318,7 @@ The wisdom of crowds for the win.
|
||||
|
||||
You may want to use Fabric to create your own custom Patterns—but not share them with others. No problem!
|
||||
|
||||
Just make a directory in `~/.config/custompatterns/` (or wherever) and put your `.md` files in there.
|
||||
Just make a directory in `~/.config/custompatterns/` (or wherever) and put your `.md` files in there.
|
||||
|
||||
When you're ready to use them, copy them into:
|
||||
|
||||
@@ -294,26 +335,6 @@ This feature works with all openai and ollama models but does NOT work with clau
|
||||
|
||||
Fabric also makes use of some core helper apps (tools) to make it easier to integrate with your various workflows. Here are some examples:
|
||||
|
||||
`yt` is a helper command that extracts the transcript from a YouTube video. You can use it like this:
|
||||
```bash
|
||||
yt https://www.youtube.com/watch?v=lQVcbY52_gY
|
||||
```
|
||||
|
||||
This will return the transcript from the video, which you can then pipe into Fabric like this:
|
||||
```bash
|
||||
yt https://www.youtube.com/watch?v=lQVcbY52_gY | fabric --pattern extract_wisdom
|
||||
```
|
||||
|
||||
### `yt` Installation
|
||||
|
||||
To install `yt`, install it the same way as you install Fabric, just with a different repo name.
|
||||
|
||||
```bash
|
||||
go install github.com/danielmiessler/yt@latest
|
||||
```
|
||||
|
||||
Be sure to add your `YOUTUBE_API_KEY` to `~/.config/fabric/.env`.
|
||||
|
||||
### `to_pdf`
|
||||
|
||||
`to_pdf` is a helper command that converts LaTeX files to PDF format. You can use it like this:
|
||||
@@ -337,11 +358,34 @@ This will create a PDF file named `output.pdf` in the current directory.
|
||||
To install `to_pdf`, install it the same way as you install Fabric, just with a different repo name.
|
||||
|
||||
```bash
|
||||
go install github.com/danielmiessler/fabric/to_pdf/to_pdf@latest
|
||||
go install github.com/danielmiessler/fabric/to_pdf@latest
|
||||
```
|
||||
|
||||
Make sure you have a LaTeX distribution (like TeX Live or MiKTeX) installed on your system, as `to_pdf` requires `pdflatex` to be available in your system's PATH.
|
||||
|
||||
## pbpaste
|
||||
|
||||
The [examples](#examples) use the macOS program `pbpaste` to paste content from the clipboard to pipe into `fabric` as the input. `pbpaste` is not available on Windows or Linux, but there are alternatives.
|
||||
|
||||
On Windows, you can use the PowerShell command `Get-Clipboard` from a PowerShell command prompt. If you like, you can also alias it to `pbpaste`. If you are using classic PowerShell, edit the file `~\Documents\WindowsPowerShell\.profile.ps1`, or if you are using PowerShell Core, edit `~\Documents\PowerShell\.profile.ps1` and add the alias,
|
||||
|
||||
```powershell
|
||||
Set-Alias pbpaste Get-Clipboard
|
||||
```
|
||||
|
||||
On Linux, you can use `xclip -selection clipboard -o` to paste from the clipboard. You will likely need to install `xclip` with your package manager. For Debian based systems including Ubuntu,
|
||||
|
||||
```sh
|
||||
sudo apt update
|
||||
sudo apt install xclip -y
|
||||
```
|
||||
|
||||
You can also create an alias by editing `~/.bashrc` or `~/.zshrc` and adding the alias,
|
||||
|
||||
```sh
|
||||
alias pbpaste='xclip -selection clipboard -o'
|
||||
```
|
||||
|
||||
## Meta
|
||||
|
||||
> [!NOTE]
|
||||
|
||||
189
cli/cli.go
189
cli/cli.go
@@ -2,21 +2,26 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/danielmiessler/fabric/core"
|
||||
"github.com/danielmiessler/fabric/plugins/ai"
|
||||
"github.com/danielmiessler/fabric/plugins/db/fsdb"
|
||||
"github.com/danielmiessler/fabric/plugins/tools/converter"
|
||||
"github.com/danielmiessler/fabric/restapi"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/core"
|
||||
"github.com/danielmiessler/fabric/db"
|
||||
)
|
||||
|
||||
// Cli Controls the cli. It takes in the flags and runs the appropriate functions
|
||||
func Cli() (message string, err error) {
|
||||
func Cli(version string) (err error) {
|
||||
var currentFlags *Flags
|
||||
if currentFlags, err = Init(); err != nil {
|
||||
// we need to reset error, because we don't want to show double help messages
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
|
||||
if currentFlags.Version {
|
||||
fmt.Println(version)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -25,40 +30,38 @@ func Cli() (message string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
fabricDb := db.NewDb(filepath.Join(homedir, ".config/fabric"))
|
||||
fabricDb := fsdb.NewDb(filepath.Join(homedir, ".config/fabric"))
|
||||
|
||||
if err = fabricDb.Configure(); err != nil {
|
||||
if !currentFlags.Setup {
|
||||
println(err.Error())
|
||||
currentFlags.Setup = true
|
||||
}
|
||||
}
|
||||
|
||||
registry := core.NewPluginRegistry(fabricDb)
|
||||
|
||||
// if the setup flag is set, run the setup function
|
||||
if currentFlags.Setup {
|
||||
_ = fabricDb.Configure()
|
||||
_, err = Setup(fabricDb, currentFlags.SetupSkipUpdatePatterns)
|
||||
err = registry.Setup()
|
||||
return
|
||||
}
|
||||
|
||||
var fabric *core.Fabric
|
||||
if err = fabricDb.Configure(); err != nil {
|
||||
fmt.Println("init is failed, run start the setup procedure", err)
|
||||
if fabric, err = Setup(fabricDb, currentFlags.SetupSkipUpdatePatterns); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if fabric, err = core.NewFabric(fabricDb); err != nil {
|
||||
fmt.Println("fabric can't initialize, please run the --setup procedure", err)
|
||||
return
|
||||
}
|
||||
if currentFlags.Serve {
|
||||
err = restapi.Serve(registry, currentFlags.ServeAddress)
|
||||
return
|
||||
}
|
||||
|
||||
// if the update patterns flag is set, run the update patterns function
|
||||
if currentFlags.UpdatePatterns {
|
||||
err = fabric.PopulateDB()
|
||||
err = registry.PatternsLoader.PopulateDB()
|
||||
return
|
||||
}
|
||||
|
||||
if currentFlags.ChangeDefaultModel {
|
||||
err = fabric.SetupDefaultModel()
|
||||
err = registry.Defaults.Setup()
|
||||
return
|
||||
}
|
||||
|
||||
// if the latest patterns flag is set, run the latest patterns function
|
||||
if currentFlags.LatestPatterns != "0" {
|
||||
var parsedToInt int
|
||||
if parsedToInt, err = strconv.Atoi(currentFlags.LatestPatterns); err != nil {
|
||||
@@ -71,30 +74,58 @@ func Cli() (message string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// if the list patterns flag is set, run the list all patterns function
|
||||
if currentFlags.ListPatterns {
|
||||
err = fabricDb.Patterns.ListNames()
|
||||
return
|
||||
}
|
||||
|
||||
// if the list all models flag is set, run the list all models function
|
||||
if currentFlags.ListAllModels {
|
||||
fabric.GetModels().Print()
|
||||
var models *ai.VendorsModels
|
||||
if models, err = registry.VendorManager.GetModels(); err != nil {
|
||||
return
|
||||
}
|
||||
models.Print()
|
||||
return
|
||||
}
|
||||
|
||||
// if the list all contexts flag is set, run the list all contexts function
|
||||
if currentFlags.ListAllContexts {
|
||||
err = fabricDb.Contexts.ListNames()
|
||||
return
|
||||
}
|
||||
|
||||
// if the list all sessions flag is set, run the list all sessions function
|
||||
if currentFlags.ListAllSessions {
|
||||
err = fabricDb.Sessions.ListNames()
|
||||
return
|
||||
}
|
||||
|
||||
if currentFlags.WipeContext != "" {
|
||||
err = fabricDb.Contexts.Delete(currentFlags.WipeContext)
|
||||
return
|
||||
}
|
||||
|
||||
if currentFlags.WipeSession != "" {
|
||||
err = fabricDb.Sessions.Delete(currentFlags.WipeSession)
|
||||
return
|
||||
}
|
||||
|
||||
if currentFlags.PrintSession != "" {
|
||||
err = fabricDb.Sessions.PrintSession(currentFlags.PrintSession)
|
||||
return
|
||||
}
|
||||
|
||||
if currentFlags.PrintContext != "" {
|
||||
err = fabricDb.Contexts.PrintContext(currentFlags.PrintContext)
|
||||
return
|
||||
}
|
||||
|
||||
if currentFlags.HtmlReadability {
|
||||
if msg, cleanErr := converter.HtmlReadability(currentFlags.Message); cleanErr != nil {
|
||||
fmt.Println("use original input, because can't apply html readability", err)
|
||||
} else {
|
||||
currentFlags.Message = msg
|
||||
}
|
||||
}
|
||||
|
||||
// if the interactive flag is set, run the interactive function
|
||||
// if currentFlags.Interactive {
|
||||
// interactive.Interactive()
|
||||
@@ -103,93 +134,115 @@ func Cli() (message string, err error) {
|
||||
// if none of the above currentFlags are set, run the initiate chat function
|
||||
|
||||
if currentFlags.YouTube != "" {
|
||||
if fabric.YouTube.IsConfigured() == false {
|
||||
if registry.YouTube.IsConfigured() == false {
|
||||
err = fmt.Errorf("YouTube is not configured, please run the setup procedure")
|
||||
return
|
||||
}
|
||||
|
||||
var videoId string
|
||||
if videoId, err = fabric.YouTube.GetVideoId(currentFlags.YouTube); err != nil {
|
||||
if videoId, err = registry.YouTube.GetVideoId(currentFlags.YouTube); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !currentFlags.YouTubeComments || currentFlags.YouTubeTranscript {
|
||||
var transcript string
|
||||
if transcript, err = fabric.YouTube.GrabTranscript(videoId); err != nil {
|
||||
var language = "en"
|
||||
if currentFlags.Language != "" || registry.Language.DefaultLanguage.Value != "" {
|
||||
if currentFlags.Language != "" {
|
||||
language = currentFlags.Language
|
||||
} else {
|
||||
language = registry.Language.DefaultLanguage.Value
|
||||
}
|
||||
}
|
||||
if transcript, err = registry.YouTube.GrabTranscript(videoId, language); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(transcript)
|
||||
|
||||
if currentFlags.Message != "" {
|
||||
currentFlags.Message = currentFlags.Message + "\n" + transcript
|
||||
} else {
|
||||
currentFlags.Message = transcript
|
||||
}
|
||||
currentFlags.AppendMessage(transcript)
|
||||
}
|
||||
|
||||
if currentFlags.YouTubeComments {
|
||||
var comments []string
|
||||
if comments, err = fabric.YouTube.GrabComments(videoId); err != nil {
|
||||
if comments, err = registry.YouTube.GrabComments(videoId); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
commentsString := strings.Join(comments, "\n")
|
||||
|
||||
fmt.Println(commentsString)
|
||||
|
||||
if currentFlags.Message != "" {
|
||||
currentFlags.Message = currentFlags.Message + "\n" + commentsString
|
||||
} else {
|
||||
currentFlags.Message = commentsString
|
||||
}
|
||||
currentFlags.AppendMessage(commentsString)
|
||||
}
|
||||
|
||||
if currentFlags.Pattern == "" {
|
||||
if !currentFlags.IsChatRequest() {
|
||||
// if the pattern flag is not set, we wanted only to grab the transcript or comments
|
||||
fmt.Println(currentFlags.Message)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (currentFlags.ScrapeURL != "" || currentFlags.ScrapeQuestion != "") && registry.Jina.IsConfigured() {
|
||||
// Check if the scrape_url flag is set and call ScrapeURL
|
||||
if currentFlags.ScrapeURL != "" {
|
||||
var website string
|
||||
if website, err = registry.Jina.ScrapeURL(currentFlags.ScrapeURL); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
currentFlags.AppendMessage(website)
|
||||
}
|
||||
|
||||
// Check if the scrape_question flag is set and call ScrapeQuestion
|
||||
if currentFlags.ScrapeQuestion != "" {
|
||||
var website string
|
||||
if website, err = registry.Jina.ScrapeQuestion(currentFlags.ScrapeQuestion); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
currentFlags.AppendMessage(website)
|
||||
}
|
||||
|
||||
if !currentFlags.IsChatRequest() {
|
||||
// if the pattern flag is not set, we wanted only to grab the url or get the answer to the question
|
||||
fmt.Println(currentFlags.Message)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var chatter *core.Chatter
|
||||
if chatter, err = fabric.GetChatter(currentFlags.Model, currentFlags.Stream, currentFlags.DryRun); err != nil {
|
||||
if chatter, err = registry.GetChatter(currentFlags.Model, currentFlags.Stream, currentFlags.DryRun); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if message, err = chatter.Send(currentFlags.BuildChatRequest(), currentFlags.BuildChatOptions()); err != nil {
|
||||
var session *fsdb.Session
|
||||
chatReq := currentFlags.BuildChatRequest(strings.Join(os.Args[1:], " "))
|
||||
if chatReq.Language == "" {
|
||||
chatReq.Language = registry.Language.DefaultLanguage.Value
|
||||
}
|
||||
if session, err = chatter.Send(chatReq, currentFlags.BuildChatOptions()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
result := session.GetLastMessage().Content
|
||||
|
||||
if !currentFlags.Stream {
|
||||
fmt.Println(message)
|
||||
// print the result if it was not streamed already
|
||||
fmt.Println(result)
|
||||
}
|
||||
|
||||
// if the copy flag is set, copy the message to the clipboard
|
||||
if currentFlags.Copy {
|
||||
if err = fabric.CopyToClipboard(message); err != nil {
|
||||
if err = CopyToClipboard(result); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// if the output flag is set, create an output file
|
||||
if currentFlags.Output != "" {
|
||||
err = fabric.CreateOutputFile(message, currentFlags.Output)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Setup(db *db.Db, skipUpdatePatterns bool) (ret *core.Fabric, err error) {
|
||||
instance := core.NewFabricForSetup(db)
|
||||
|
||||
if err = instance.Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !skipUpdatePatterns {
|
||||
if err = instance.PopulateDB(); err != nil {
|
||||
return
|
||||
if currentFlags.OutputSession {
|
||||
sessionAsString := session.String()
|
||||
err = CreateOutputFile(sessionAsString, currentFlags.Output)
|
||||
} else {
|
||||
err = CreateOutputFile(result, currentFlags.Output)
|
||||
}
|
||||
}
|
||||
ret = instance
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/core"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/danielmiessler/fabric/db"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCli(t *testing.T) {
|
||||
message, err := Cli()
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, message)
|
||||
}
|
||||
t.Skip("Skipping test for now, collision with flag -t")
|
||||
originalArgs := os.Args
|
||||
defer func() { os.Args = originalArgs }()
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
mockDB := db.NewDb(os.TempDir())
|
||||
|
||||
fabric, err := Setup(mockDB, false)
|
||||
os.Args = []string{os.Args[0]}
|
||||
err := Cli("test")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, fabric)
|
||||
assert.Equal(t, core.NoSessionPatternUserMessages, err.Error())
|
||||
}
|
||||
|
||||
91
cli/flags.go
91
cli/flags.go
@@ -9,36 +9,50 @@ import (
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/jessevdk/go-flags"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
// Flags create flags struct. the users flags go into this, this will be passed to the chat struct in cli
|
||||
type Flags struct {
|
||||
Pattern string `short:"p" long:"pattern" description:"Choose a pattern" default:""`
|
||||
PatternVariables map[string]string `short:"v" long:"variable" description:"Values for pattern variables, e.g. -v=$name:John -v=$age:30"`
|
||||
Context string `short:"C" long:"context" description:"Choose a context" default:""`
|
||||
Session string `long:"session" description:"Choose a session"`
|
||||
Setup bool `short:"S" long:"setup" description:"Run setup"`
|
||||
SetupSkipUpdatePatterns bool `long:"setup-skip-update-patterns" description:"Skip update patterns at setup"`
|
||||
Temperature float64 `short:"t" long:"temperature" description:"Set temperature" default:"0.7"`
|
||||
TopP float64 `short:"T" long:"topp" description:"Set top P" default:"0.9"`
|
||||
Stream bool `short:"s" long:"stream" description:"Stream"`
|
||||
PresencePenalty float64 `short:"P" long:"presencepenalty" description:"Set presence penalty" default:"0.0"`
|
||||
FrequencyPenalty float64 `short:"F" long:"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:"Message to send to chat"`
|
||||
Copy bool `short:"c" long:"copy" description:"Copy to clipboard"`
|
||||
Model string `short:"m" long:"model" description:"Choose model"`
|
||||
Output string `short:"o" long:"output" description:"Output to file" default:""`
|
||||
LatestPatterns string `short:"n" long:"latest" description:"Number of latest patterns to list" default:"0"`
|
||||
ChangeDefaultModel bool `short:"d" long:"changeDefaultModel" description:"Change default pattern"`
|
||||
YouTube string `short:"y" long:"youtube" description:"YouTube video url to grab transcript, comments from it and send to chat"`
|
||||
YouTubeTranscript bool `long:"transcript" description:"Grab transcript from YouTube video and send to chat"`
|
||||
YouTubeComments bool `long:"comments" description:"Grab comments from YouTube video and send to chat"`
|
||||
DryRun bool `long:"dry-run" description:"Show what would be sent to the model without actually sending it"`
|
||||
Pattern string `short:"p" long:"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"`
|
||||
Setup bool `short:"S" long:"setup" description:"Run setup for all reconfigurable parts of fabric"`
|
||||
Temperature float64 `short:"t" long:"temperature" description:"Set temperature" default:"0.7"`
|
||||
TopP float64 `short:"T" long:"topp" description:"Set top P" default:"0.9"`
|
||||
Stream bool `short:"s" long:"stream" description:"Stream"`
|
||||
PresencePenalty float64 `short:"P" long:"presencepenalty" description:"Set presence penalty" default:"0.0"`
|
||||
Raw bool `short:"r" long:"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" 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:"Message to send to chat"`
|
||||
Copy bool `short:"c" long:"copy" description:"Copy to clipboard"`
|
||||
Model string `short:"m" long:"model" description:"Choose model"`
|
||||
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 \"URL\" to grab transcript, comments from it and send to chat"`
|
||||
YouTubeTranscript bool `long:"transcript" description:"Grab transcript from YouTube video and send to chat (it used per default)."`
|
||||
YouTubeComments bool `long:"comments" description:"Grab comments from YouTube video and send to chat"`
|
||||
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" 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"`
|
||||
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"`
|
||||
ServeAddress string `long:"address" description:"The address to bind the REST API" default:":8080"`
|
||||
Version bool `long:"version" description:"Print current version"`
|
||||
}
|
||||
|
||||
// Init Initialize flags. returns a Flags struct and an error
|
||||
@@ -93,17 +107,40 @@ func (o *Flags) BuildChatOptions() (ret *common.ChatOptions) {
|
||||
TopP: o.TopP,
|
||||
PresencePenalty: o.PresencePenalty,
|
||||
FrequencyPenalty: o.FrequencyPenalty,
|
||||
Raw: o.Raw,
|
||||
Seed: o.Seed,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Flags) BuildChatRequest() (ret *common.ChatRequest) {
|
||||
func (o *Flags) BuildChatRequest(Meta string) (ret *common.ChatRequest) {
|
||||
ret = &common.ChatRequest{
|
||||
ContextName: o.Context,
|
||||
SessionName: o.Session,
|
||||
PatternName: o.Pattern,
|
||||
PatternVariables: o.PatternVariables,
|
||||
Message: o.Message,
|
||||
Meta: Meta,
|
||||
}
|
||||
if o.Language != "" {
|
||||
langTag, err := language.Parse(o.Language)
|
||||
if err == nil {
|
||||
ret.Language = langTag.String()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Flags) AppendMessage(message string) {
|
||||
if o.Message != "" {
|
||||
o.Message = o.Message + "\n" + message
|
||||
} else {
|
||||
o.Message = message
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Flags) IsChatRequest() (ret bool) {
|
||||
ret = (o.Message != "" || o.Context != "") && (o.Session != "" || o.Pattern != "")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ func TestBuildChatOptions(t *testing.T) {
|
||||
TopP: 0.9,
|
||||
PresencePenalty: 0.1,
|
||||
FrequencyPenalty: 0.2,
|
||||
Seed: 1,
|
||||
}
|
||||
|
||||
expectedOptions := &common.ChatOptions{
|
||||
@@ -60,6 +61,28 @@ func TestBuildChatOptions(t *testing.T) {
|
||||
TopP: 0.9,
|
||||
PresencePenalty: 0.1,
|
||||
FrequencyPenalty: 0.2,
|
||||
Raw: false,
|
||||
Seed: 1,
|
||||
}
|
||||
options := flags.BuildChatOptions()
|
||||
assert.Equal(t, expectedOptions, options)
|
||||
}
|
||||
|
||||
func TestBuildChatOptionsDefaultSeed(t *testing.T) {
|
||||
flags := &Flags{
|
||||
Temperature: 0.8,
|
||||
TopP: 0.9,
|
||||
PresencePenalty: 0.1,
|
||||
FrequencyPenalty: 0.2,
|
||||
}
|
||||
|
||||
expectedOptions := &common.ChatOptions{
|
||||
Temperature: 0.8,
|
||||
TopP: 0.9,
|
||||
PresencePenalty: 0.1,
|
||||
FrequencyPenalty: 0.2,
|
||||
Raw: false,
|
||||
Seed: 0,
|
||||
}
|
||||
options := flags.BuildChatOptions()
|
||||
assert.Equal(t, expectedOptions, options)
|
||||
@@ -78,7 +101,8 @@ func TestBuildChatRequest(t *testing.T) {
|
||||
SessionName: "test-session",
|
||||
PatternName: "test-pattern",
|
||||
Message: "test-message",
|
||||
Meta: "test",
|
||||
}
|
||||
request := flags.BuildChatRequest()
|
||||
request := flags.BuildChatRequest("test")
|
||||
assert.Equal(t, expectedRequest, request)
|
||||
}
|
||||
|
||||
27
cli/output.go
Normal file
27
cli/output.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/atotto/clipboard"
|
||||
"os"
|
||||
)
|
||||
|
||||
func CopyToClipboard(message string) (err error) {
|
||||
if err = clipboard.WriteAll(message); err != nil {
|
||||
err = fmt.Errorf("could not copy to clipboard: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CreateOutputFile(message string, fileName string) (err error) {
|
||||
var file *os.File
|
||||
if file, err = os.Create(fileName); err != nil {
|
||||
err = fmt.Errorf("error creating file: %v", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
if _, err = file.WriteString(message); err != nil {
|
||||
err = fmt.Errorf("error writing to file: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
28
cli/output_test.go
Normal file
28
cli/output_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCopyToClipboard(t *testing.T) {
|
||||
t.Skip("skipping test, because of docker env. in ci.")
|
||||
|
||||
message := "test message"
|
||||
err := CopyToClipboard(message)
|
||||
if err != nil {
|
||||
t.Fatalf("CopyToClipboard() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateOutputFile(t *testing.T) {
|
||||
|
||||
fileName := "test_output.txt"
|
||||
message := "test message"
|
||||
err := CreateOutputFile(message, fileName)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateOutputFile() error = %v", err)
|
||||
}
|
||||
|
||||
defer os.Remove(fileName)
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
package common
|
||||
|
||||
import goopenai "github.com/sashabaranov/go-openai"
|
||||
|
||||
const ChatMessageRoleMeta = "meta"
|
||||
|
||||
type Message struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
@@ -11,6 +15,8 @@ type ChatRequest struct {
|
||||
PatternName string
|
||||
PatternVariables map[string]string
|
||||
Message string
|
||||
Language string
|
||||
Meta string
|
||||
}
|
||||
|
||||
type ChatOptions struct {
|
||||
@@ -19,6 +25,8 @@ type ChatOptions struct {
|
||||
TopP float64
|
||||
PresencePenalty float64
|
||||
FrequencyPenalty float64
|
||||
Raw bool
|
||||
Seed int
|
||||
}
|
||||
|
||||
// NormalizeMessages remove empty messages and ensure messages order user-assist-user
|
||||
@@ -32,8 +40,8 @@ func NormalizeMessages(msgs []*Message, defaultUserMessage string) (ret []*Messa
|
||||
}
|
||||
|
||||
// Ensure, that each odd position shall be a user message
|
||||
if fullMessageIndex%2 == 0 && message.Role != "user" {
|
||||
ret = append(ret, &Message{Role: "user", Content: defaultUserMessage})
|
||||
if fullMessageIndex%2 == 0 && message.Role != goopenai.ChatMessageRoleUser {
|
||||
ret = append(ret, &Message{Role: goopenai.ChatMessageRoleUser, Content: defaultUserMessage})
|
||||
fullMessageIndex++
|
||||
}
|
||||
ret = append(ret, message)
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNormalizeMessages(t *testing.T) {
|
||||
msgs := []*Message{
|
||||
{Role: "user", Content: "Hello"},
|
||||
{Role: "bot", Content: "Hi there!"},
|
||||
{Role: "bot", Content: ""},
|
||||
{Role: "user", Content: ""},
|
||||
{Role: "user", Content: "How are you?"},
|
||||
{Role: goopenai.ChatMessageRoleUser, Content: "Hello"},
|
||||
{Role: goopenai.ChatMessageRoleAssistant, Content: "Hi there!"},
|
||||
{Role: goopenai.ChatMessageRoleUser, Content: ""},
|
||||
{Role: goopenai.ChatMessageRoleUser, Content: ""},
|
||||
{Role: goopenai.ChatMessageRoleUser, Content: "How are you?"},
|
||||
}
|
||||
|
||||
expected := []*Message{
|
||||
{Role: "user", Content: "Hello"},
|
||||
{Role: "bot", Content: "Hi there!"},
|
||||
{Role: "user", Content: "How are you?"},
|
||||
{Role: goopenai.ChatMessageRoleUser, Content: "Hello"},
|
||||
{Role: goopenai.ChatMessageRoleAssistant, Content: "Hi there!"},
|
||||
{Role: goopenai.ChatMessageRoleUser, Content: "How are you?"},
|
||||
}
|
||||
|
||||
actual := NormalizeMessages(msgs, "default")
|
||||
|
||||
134
common/groups_items.go
Normal file
134
common/groups_items.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func NewGroupsItemsSelector[I any](selectionLabel string,
|
||||
getItemLabel func(I) string) *GroupsItemsSelector[I] {
|
||||
|
||||
return &GroupsItemsSelector[I]{SelectionLabel: selectionLabel,
|
||||
GetItemKey: getItemLabel,
|
||||
GroupsItems: make([]*GroupItems[I], 0),
|
||||
}
|
||||
}
|
||||
|
||||
type GroupItems[I any] struct {
|
||||
Group string
|
||||
Items []I
|
||||
}
|
||||
|
||||
func (o *GroupItems[I]) Count() int {
|
||||
return len(o.Items)
|
||||
}
|
||||
|
||||
func (o *GroupItems[I]) ContainsItemBy(predicate func(item I) bool) (ret bool) {
|
||||
ret = lo.ContainsBy(o.Items, predicate)
|
||||
return
|
||||
}
|
||||
|
||||
type GroupsItemsSelector[I any] struct {
|
||||
SelectionLabel string
|
||||
GetItemKey func(I) string
|
||||
|
||||
GroupsItems []*GroupItems[I]
|
||||
}
|
||||
|
||||
func (o *GroupsItemsSelector[I]) AddGroupItems(group string, items ...I) {
|
||||
o.GroupsItems = append(o.GroupsItems, &GroupItems[I]{group, items})
|
||||
}
|
||||
|
||||
func (o *GroupsItemsSelector[I]) GetGroupAndItemByItemNumber(number int) (group string, item I, err error) {
|
||||
var currentItemNumber int
|
||||
found := false
|
||||
|
||||
for _, groupItems := range o.GroupsItems {
|
||||
if currentItemNumber+groupItems.Count() < number {
|
||||
currentItemNumber += groupItems.Count()
|
||||
continue
|
||||
}
|
||||
|
||||
for _, groupItem := range groupItems.Items {
|
||||
currentItemNumber++
|
||||
if currentItemNumber == number {
|
||||
group = groupItems.Group
|
||||
item = groupItem
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
err = fmt.Errorf("number %d is out of range", number)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *GroupsItemsSelector[I]) Print() {
|
||||
fmt.Printf("\n%v:\n", o.SelectionLabel)
|
||||
|
||||
var currentItemIndex int
|
||||
for _, groupItems := range o.GroupsItems {
|
||||
fmt.Println()
|
||||
fmt.Printf("%s\n", groupItems.Group)
|
||||
fmt.Println()
|
||||
|
||||
for _, item := range groupItems.Items {
|
||||
currentItemIndex++
|
||||
fmt.Printf("\t[%d]\t%s\n", currentItemIndex, o.GetItemKey(item))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *GroupsItemsSelector[I]) HasGroup(group string) (ret bool) {
|
||||
for _, groupItems := range o.GroupsItems {
|
||||
if ret = groupItems.Group == group; ret {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *GroupsItemsSelector[I]) FindGroupsByItemFirst(item I) (ret string) {
|
||||
itemKey := o.GetItemKey(item)
|
||||
|
||||
for _, groupItems := range o.GroupsItems {
|
||||
if groupItems.ContainsItemBy(func(groupItem I) bool {
|
||||
groupItemKey := o.GetItemKey(groupItem)
|
||||
return groupItemKey == itemKey
|
||||
}) {
|
||||
ret = groupItems.Group
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *GroupsItemsSelector[I]) FindGroupsByItem(item I) (groups []string) {
|
||||
itemKey := o.GetItemKey(item)
|
||||
|
||||
for _, groupItems := range o.GroupsItems {
|
||||
if groupItems.ContainsItemBy(func(groupItem I) bool {
|
||||
groupItemKey := o.GetItemKey(groupItem)
|
||||
return groupItemKey == itemKey
|
||||
}) {
|
||||
groups = append(groups, groupItems.Group)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReturnItem(item string) string {
|
||||
return item
|
||||
}
|
||||
|
||||
func NewGroupsItemsSelectorString(selectionLabel string) *GroupsItemsSelectorString {
|
||||
return &GroupsItemsSelectorString{GroupsItemsSelector: NewGroupsItemsSelector(selectionLabel, ReturnItem)}
|
||||
}
|
||||
|
||||
type GroupsItemsSelectorString struct {
|
||||
*GroupsItemsSelector[string]
|
||||
}
|
||||
117
core/chatter.go
117
core/chatter.go
@@ -3,30 +3,27 @@ package core
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/danielmiessler/fabric/db"
|
||||
"github.com/danielmiessler/fabric/vendors"
|
||||
"github.com/danielmiessler/fabric/plugins/ai"
|
||||
"github.com/danielmiessler/fabric/plugins/db/fsdb"
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const NoSessionPatternUserMessages = "no session, pattern or user messages provided"
|
||||
|
||||
type Chatter struct {
|
||||
db *db.Db
|
||||
db *fsdb.Db
|
||||
|
||||
Stream bool
|
||||
DryRun bool
|
||||
|
||||
model string
|
||||
vendor vendors.Vendor
|
||||
vendor ai.Vendor
|
||||
}
|
||||
|
||||
func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (message string, err error) {
|
||||
var chatRequest *Chat
|
||||
if chatRequest, err = o.NewChat(request); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var session *db.Session
|
||||
if session, err = chatRequest.BuildChatSession(); err != nil {
|
||||
func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (session *fsdb.Session, err error) {
|
||||
if session, err = o.BuildSession(request, opts.Raw); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -34,10 +31,12 @@ func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (m
|
||||
opts.Model = o.model
|
||||
}
|
||||
|
||||
message := ""
|
||||
|
||||
if o.Stream {
|
||||
channel := make(chan string)
|
||||
go func() {
|
||||
if streamErr := o.vendor.SendStream(session.Messages, opts, channel); streamErr != nil {
|
||||
if streamErr := o.vendor.SendStream(session.GetVendorMessages(), opts, channel); streamErr != nil {
|
||||
channel <- streamErr.Error()
|
||||
}
|
||||
}()
|
||||
@@ -47,58 +46,88 @@ func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (m
|
||||
fmt.Print(response)
|
||||
}
|
||||
} else {
|
||||
if message, err = o.vendor.Send(context.Background(), session.Messages, opts); err != nil {
|
||||
if message, err = o.vendor.Send(context.Background(), session.GetVendorMessages(), opts); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if chatRequest.Session != nil && message != "" {
|
||||
chatRequest.Session.Append(&common.Message{Role: "system", Content: message})
|
||||
err = o.db.Sessions.SaveSession(chatRequest.Session)
|
||||
if message == "" {
|
||||
session = nil
|
||||
err = fmt.Errorf("empty response")
|
||||
return
|
||||
}
|
||||
|
||||
session.Append(&common.Message{Role: goopenai.ChatMessageRoleAssistant, Content: message})
|
||||
|
||||
if session.Name != "" {
|
||||
err = o.db.Sessions.SaveSession(session)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Chatter) NewChat(request *common.ChatRequest) (ret *Chat, err error) {
|
||||
ret = &Chat{}
|
||||
|
||||
if request.ContextName != "" {
|
||||
var ctx *db.Context
|
||||
if ctx, err = o.db.Contexts.GetContext(request.ContextName); err != nil {
|
||||
err = fmt.Errorf("could not find context %s: %v", request.ContextName, err)
|
||||
return
|
||||
}
|
||||
ret.Context = ctx.Content
|
||||
}
|
||||
|
||||
func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *fsdb.Session, err error) {
|
||||
if request.SessionName != "" {
|
||||
var sess *db.Session
|
||||
if sess, err = o.db.Sessions.GetOrCreateSession(request.SessionName); err != nil {
|
||||
var sess *fsdb.Session
|
||||
if sess, err = o.db.Sessions.Get(request.SessionName); err != nil {
|
||||
err = fmt.Errorf("could not find session %s: %v", request.SessionName, err)
|
||||
return
|
||||
}
|
||||
ret.Session = sess
|
||||
session = sess
|
||||
} else {
|
||||
session = &fsdb.Session{}
|
||||
}
|
||||
|
||||
if request.Meta != "" {
|
||||
session.Append(&common.Message{Role: common.ChatMessageRoleMeta, Content: request.Meta})
|
||||
}
|
||||
|
||||
var contextContent string
|
||||
if request.ContextName != "" {
|
||||
var ctx *fsdb.Context
|
||||
if ctx, err = o.db.Contexts.Get(request.ContextName); err != nil {
|
||||
err = fmt.Errorf("could not find context %s: %v", request.ContextName, err)
|
||||
return
|
||||
}
|
||||
contextContent = ctx.Content
|
||||
}
|
||||
|
||||
var patternContent string
|
||||
if request.PatternName != "" {
|
||||
var pattern *db.Pattern
|
||||
if pattern, err = o.db.Patterns.GetPattern(request.PatternName, request.PatternVariables); err != nil {
|
||||
var pattern *fsdb.Pattern
|
||||
if pattern, err = o.db.Patterns.GetApplyVariables(request.PatternName, request.PatternVariables); err != nil {
|
||||
err = fmt.Errorf("could not find pattern %s: %v", request.PatternName, err)
|
||||
return
|
||||
}
|
||||
|
||||
if pattern.Pattern != "" {
|
||||
ret.Pattern = pattern.Pattern
|
||||
patternContent = pattern.Pattern
|
||||
}
|
||||
}
|
||||
|
||||
ret.Message = request.Message
|
||||
systemMessage := strings.TrimSpace(contextContent) + strings.TrimSpace(patternContent)
|
||||
if request.Language != "" {
|
||||
systemMessage = fmt.Sprintf("%s. Please use the language '%s' for the output.", systemMessage, request.Language)
|
||||
}
|
||||
userMessage := strings.TrimSpace(request.Message)
|
||||
|
||||
if raw {
|
||||
// use the user role instead of the system role in raw mode
|
||||
message := systemMessage + userMessage
|
||||
if message != "" {
|
||||
session.Append(&common.Message{Role: goopenai.ChatMessageRoleUser, Content: message})
|
||||
}
|
||||
} else {
|
||||
if systemMessage != "" {
|
||||
session.Append(&common.Message{Role: goopenai.ChatMessageRoleSystem, Content: systemMessage})
|
||||
}
|
||||
if userMessage != "" {
|
||||
session.Append(&common.Message{Role: goopenai.ChatMessageRoleUser, Content: userMessage})
|
||||
}
|
||||
}
|
||||
|
||||
if session.IsEmpty() {
|
||||
session = nil
|
||||
err = fmt.Errorf(NoSessionPatternUserMessages)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Chat struct {
|
||||
Context string
|
||||
Pattern string
|
||||
Message string
|
||||
Session *db.Session
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildChatSession(t *testing.T) {
|
||||
chat := &Chat{
|
||||
Context: "test context",
|
||||
Pattern: "test pattern",
|
||||
Message: "test message",
|
||||
}
|
||||
session, err := chat.BuildChatSession()
|
||||
if err != nil {
|
||||
t.Fatalf("BuildChatSession() error = %v", err)
|
||||
}
|
||||
|
||||
if session == nil {
|
||||
t.Fatalf("BuildChatSession() returned nil session")
|
||||
}
|
||||
}
|
||||
263
core/fabric.go
263
core/fabric.go
@@ -1,263 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/danielmiessler/fabric/vendors/groq"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/atotto/clipboard"
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/danielmiessler/fabric/db"
|
||||
"github.com/danielmiessler/fabric/vendors/anthropic"
|
||||
"github.com/danielmiessler/fabric/vendors/azure"
|
||||
"github.com/danielmiessler/fabric/vendors/dryrun"
|
||||
"github.com/danielmiessler/fabric/vendors/gemini"
|
||||
"github.com/danielmiessler/fabric/vendors/ollama"
|
||||
"github.com/danielmiessler/fabric/vendors/openai"
|
||||
"github.com/danielmiessler/fabric/vendors/openrouter"
|
||||
"github.com/danielmiessler/fabric/vendors/siliconcloud"
|
||||
"github.com/danielmiessler/fabric/youtube"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const DefaultPatternsGitRepoUrl = "https://github.com/danielmiessler/fabric.git"
|
||||
const DefaultPatternsGitRepoFolder = "patterns"
|
||||
|
||||
func NewFabric(db *db.Db) (ret *Fabric, err error) {
|
||||
ret = NewFabricBase(db)
|
||||
err = ret.Configure()
|
||||
return
|
||||
}
|
||||
|
||||
func NewFabricForSetup(db *db.Db) (ret *Fabric) {
|
||||
ret = NewFabricBase(db)
|
||||
_ = ret.Configure()
|
||||
return
|
||||
}
|
||||
|
||||
// NewFabricBase Create a new Fabric from a list of already configured VendorsController
|
||||
func NewFabricBase(db *db.Db) (ret *Fabric) {
|
||||
|
||||
ret = &Fabric{
|
||||
VendorsManager: NewVendorsManager(),
|
||||
Db: db,
|
||||
VendorsAll: NewVendorsManager(),
|
||||
PatternsLoader: NewPatternsLoader(db.Patterns),
|
||||
YouTube: youtube.NewYouTube(),
|
||||
}
|
||||
|
||||
label := "Default"
|
||||
ret.Configurable = &common.Configurable{
|
||||
Label: label,
|
||||
EnvNamePrefix: common.BuildEnvVariablePrefix(label),
|
||||
ConfigureCustom: ret.configure,
|
||||
}
|
||||
|
||||
ret.DefaultVendor = ret.AddSetting("Vendor", true)
|
||||
ret.DefaultModel = ret.AddSetupQuestionCustom("Model", true,
|
||||
"Enter the index the name of your default model")
|
||||
|
||||
ret.VendorsAll.AddVendors(openai.NewClient(), azure.NewClient(), ollama.NewClient(), groq.NewClient(),
|
||||
gemini.NewClient(), anthropic.NewClient(), siliconcloud.NewClient(), openrouter.NewClient())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Fabric struct {
|
||||
*common.Configurable
|
||||
*VendorsManager
|
||||
VendorsAll *VendorsManager
|
||||
*PatternsLoader
|
||||
*youtube.YouTube
|
||||
|
||||
Db *db.Db
|
||||
|
||||
DefaultVendor *common.Setting
|
||||
DefaultModel *common.SetupQuestion
|
||||
}
|
||||
|
||||
type ChannelName struct {
|
||||
channel chan []string
|
||||
name string
|
||||
}
|
||||
|
||||
func (o *Fabric) SaveEnvFile() (err error) {
|
||||
// Now create the .env with all configured VendorsController info
|
||||
var envFileContent bytes.Buffer
|
||||
|
||||
o.Settings.FillEnvFileContent(&envFileContent)
|
||||
o.PatternsLoader.SetupFillEnvFileContent(&envFileContent)
|
||||
|
||||
for _, vendor := range o.Vendors {
|
||||
vendor.SetupFillEnvFileContent(&envFileContent)
|
||||
}
|
||||
|
||||
o.YouTube.SetupFillEnvFileContent(&envFileContent)
|
||||
|
||||
err = o.Db.SaveEnv(envFileContent.String())
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Fabric) Setup() (err error) {
|
||||
if err = o.SetupVendors(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = o.SetupDefaultModel(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_ = o.YouTube.SetupOrSkip()
|
||||
|
||||
if err = o.PatternsLoader.Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = o.SaveEnvFile()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Fabric) SetupDefaultModel() (err error) {
|
||||
vendorsModels := o.GetModels()
|
||||
|
||||
vendorsModels.Print()
|
||||
|
||||
if err = o.Ask(o.Label); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
index, parseErr := strconv.Atoi(o.DefaultModel.Value)
|
||||
if parseErr == nil {
|
||||
o.DefaultVendor.Value, o.DefaultModel.Value = vendorsModels.GetVendorAndModelByModelIndex(index)
|
||||
} else {
|
||||
o.DefaultVendor.Value = vendorsModels.FindVendorsByModelFirst(o.DefaultModel.Value)
|
||||
}
|
||||
|
||||
//verify
|
||||
vendorNames := vendorsModels.FindVendorsByModel(o.DefaultModel.Value)
|
||||
if len(vendorNames) == 0 {
|
||||
err = errors.Errorf("You need to chose an available default model.")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
o.DefaultVendor.Print()
|
||||
o.DefaultModel.Print()
|
||||
|
||||
err = o.SaveEnvFile()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Fabric) SetupVendors() (err error) {
|
||||
o.Models = nil
|
||||
if o.Vendors, err = o.VendorsAll.Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !o.HasVendors() {
|
||||
err = errors.New("No vendors configured")
|
||||
return
|
||||
}
|
||||
|
||||
err = o.SaveEnvFile()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Configure buildClient VendorsController based on the environment variables
|
||||
func (o *Fabric) configure() (err error) {
|
||||
for _, vendor := range o.VendorsAll.Vendors {
|
||||
if vendorErr := vendor.Configure(); vendorErr == nil {
|
||||
o.AddVendors(vendor)
|
||||
}
|
||||
}
|
||||
if err = o.PatternsLoader.Configure(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//YouTube is not mandatory, so ignore not configured error
|
||||
_ = o.YouTube.Configure()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Fabric) GetChatter(model string, stream bool, dryRun bool) (ret *Chatter, err error) {
|
||||
ret = &Chatter{
|
||||
db: o.Db,
|
||||
Stream: stream,
|
||||
DryRun: dryRun,
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
ret.vendor = dryrun.NewClient()
|
||||
ret.model = model
|
||||
if ret.model == "" {
|
||||
ret.model = o.DefaultModel.Value
|
||||
}
|
||||
} else if model == "" {
|
||||
ret.vendor = o.FindByName(o.DefaultVendor.Value)
|
||||
ret.model = o.DefaultModel.Value
|
||||
} else {
|
||||
ret.vendor = o.FindByName(o.GetModels().FindVendorsByModelFirst(model))
|
||||
ret.model = model
|
||||
}
|
||||
|
||||
if ret.vendor == nil {
|
||||
err = fmt.Errorf(
|
||||
"could not find vendor.\n Model = %s\n DefaultModel = %s\n DefaultVendor = %s",
|
||||
model, o.DefaultModel.Value, o.DefaultVendor.Value)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Fabric) CopyToClipboard(message string) (err error) {
|
||||
if err = clipboard.WriteAll(message); err != nil {
|
||||
err = fmt.Errorf("could not copy to clipboard: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Fabric) CreateOutputFile(message string, fileName string) (err error) {
|
||||
var file *os.File
|
||||
if file, err = os.Create(fileName); err != nil {
|
||||
err = fmt.Errorf("error creating file: %v", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
if _, err = file.WriteString(message); err != nil {
|
||||
err = fmt.Errorf("error writing to file: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Chat) BuildChatSession() (ret *db.Session, err error) {
|
||||
// new messages will be appended to the session and used to send the message
|
||||
if o.Session != nil {
|
||||
ret = o.Session
|
||||
} else {
|
||||
ret = &db.Session{}
|
||||
}
|
||||
|
||||
systemMessage := strings.TrimSpace(o.Context) + strings.TrimSpace(o.Pattern)
|
||||
|
||||
if systemMessage != "" {
|
||||
ret.Append(&common.Message{Role: "system", Content: systemMessage})
|
||||
}
|
||||
|
||||
userMessage := strings.TrimSpace(o.Message)
|
||||
if userMessage != "" {
|
||||
ret.Append(&common.Message{Role: "user", Content: userMessage})
|
||||
}
|
||||
|
||||
if ret.IsEmpty() {
|
||||
ret = nil
|
||||
err = fmt.Errorf("no session, pattern or user messages provided")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/danielmiessler/fabric/db"
|
||||
)
|
||||
|
||||
func TestNewFabric(t *testing.T) {
|
||||
_, err := NewFabric(db.NewDb(os.TempDir()))
|
||||
if err == nil {
|
||||
t.Fatal("without setup error expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveEnvFile(t *testing.T) {
|
||||
fabric := NewFabricBase(db.NewDb(os.TempDir()))
|
||||
|
||||
err := fabric.SaveEnvFile()
|
||||
if err != nil {
|
||||
t.Fatalf("SaveEnvFile() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyToClipboard(t *testing.T) {
|
||||
t.Skip("skipping test, because of docker env. in ci.")
|
||||
fabric := NewFabricBase(db.NewDb(os.TempDir()))
|
||||
|
||||
message := "test message"
|
||||
err := fabric.CopyToClipboard(message)
|
||||
if err != nil {
|
||||
t.Fatalf("CopyToClipboard() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateOutputFile(t *testing.T) {
|
||||
mockDb := &db.Db{}
|
||||
fabric := NewFabricBase(mockDb)
|
||||
|
||||
fileName := "test_output.txt"
|
||||
message := "test message"
|
||||
err := fabric.CreateOutputFile(message, fileName)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateOutputFile() error = %v", err)
|
||||
}
|
||||
|
||||
defer os.Remove(fileName)
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func NewVendorsModels() *VendorsModels {
|
||||
return &VendorsModels{VendorsModels: make(map[string][]string)}
|
||||
}
|
||||
|
||||
type VendorsModels struct {
|
||||
Vendors []string
|
||||
VendorsModels map[string][]string
|
||||
Errs []error
|
||||
}
|
||||
|
||||
func (o *VendorsModels) AddVendorModels(vendor string, models []string) {
|
||||
o.Vendors = append(o.Vendors, vendor)
|
||||
o.VendorsModels[vendor] = models
|
||||
}
|
||||
|
||||
func (o *VendorsModels) GetVendorAndModelByModelIndex(modelIndex int) (vendor string, model string) {
|
||||
vendorModelIndexFrom := 0
|
||||
vendorModelIndexTo := 0
|
||||
for _, currenVendor := range o.Vendors {
|
||||
vendorModelIndexFrom = vendorModelIndexTo + 1
|
||||
vendorModelIndexTo = vendorModelIndexFrom + len(o.VendorsModels[currenVendor]) - 1
|
||||
|
||||
if modelIndex >= vendorModelIndexFrom && modelIndex <= vendorModelIndexTo {
|
||||
vendor = currenVendor
|
||||
model = o.VendorsModels[currenVendor][modelIndex-vendorModelIndexFrom]
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *VendorsModels) AddError(err error) {
|
||||
o.Errs = append(o.Errs, err)
|
||||
}
|
||||
|
||||
func (o *VendorsModels) Print() {
|
||||
fmt.Printf("\nAvailable vendor models:\n")
|
||||
|
||||
sort.Strings(o.Vendors)
|
||||
|
||||
var currentModelIndex int
|
||||
for _, vendor := range o.Vendors {
|
||||
fmt.Println()
|
||||
fmt.Printf("%s\n", vendor)
|
||||
fmt.Println()
|
||||
currentModelIndex = o.PrintVendor(vendor, currentModelIndex)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *VendorsModels) PrintVendor(vendor string, modelIndex int) (currentModelIndex int) {
|
||||
currentModelIndex = modelIndex
|
||||
models := o.VendorsModels[vendor]
|
||||
for _, model := range models {
|
||||
currentModelIndex++
|
||||
fmt.Printf("\t[%d]\t%s\n", currentModelIndex, model)
|
||||
}
|
||||
fmt.Println()
|
||||
return
|
||||
}
|
||||
|
||||
func (o *VendorsModels) GetVendorModels(vendor string) (models []string) {
|
||||
models = o.VendorsModels[vendor]
|
||||
return
|
||||
}
|
||||
|
||||
func (o *VendorsModels) HasVendor(vendor string) (ret bool) {
|
||||
ret = o.VendorsModels[vendor] != nil
|
||||
return
|
||||
}
|
||||
|
||||
func (o *VendorsModels) FindVendorsByModelFirst(model string) (ret string) {
|
||||
vendors := o.FindVendorsByModel(model)
|
||||
if len(vendors) > 0 {
|
||||
ret = vendors[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *VendorsModels) FindVendorsByModel(model string) (vendors []string) {
|
||||
for vendor, models := range o.VendorsModels {
|
||||
for _, m := range models {
|
||||
if m == model {
|
||||
vendors = append(vendors, vendor)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewVendorsModels(t *testing.T) {
|
||||
vendors := NewVendorsModels()
|
||||
if vendors == nil {
|
||||
t.Fatalf("NewVendorsModels() returned nil")
|
||||
}
|
||||
if len(vendors.VendorsModels) != 0 {
|
||||
t.Fatalf("NewVendorsModels() returned non-empty VendorsModels map")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindVendorsByModelFirst(t *testing.T) {
|
||||
vendors := NewVendorsModels()
|
||||
vendors.AddVendorModels("vendor1", []string{"model1", "model2"})
|
||||
vendor := vendors.FindVendorsByModelFirst("model1")
|
||||
if vendor != "vendor1" {
|
||||
t.Fatalf("FindVendorsByModelFirst() = %v, want %v", vendor, "vendor1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindVendorsByModel(t *testing.T) {
|
||||
vendors := NewVendorsModels()
|
||||
vendors.AddVendorModels("vendor1", []string{"model1", "model2"})
|
||||
foundVendors := vendors.FindVendorsByModel("model1")
|
||||
if len(foundVendors) != 1 || foundVendors[0] != "vendor1" {
|
||||
t.Fatalf("FindVendorsByModel() = %v, want %v", foundVendors, []string{"vendor1"})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddVendorModels(t *testing.T) {
|
||||
vendors := NewVendorsModels()
|
||||
vendors.AddVendorModels("vendor1", []string{"model1", "model2"})
|
||||
models := vendors.GetVendorModels("vendor1")
|
||||
if len(models) != 2 {
|
||||
t.Fatalf("AddVendorModels() failed to add models")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddError(t *testing.T) {
|
||||
vendors := NewVendorsModels()
|
||||
err := errors.New("sample error")
|
||||
vendors.AddError(err)
|
||||
if len(vendors.Errs) != 1 {
|
||||
t.Fatalf("AddError() failed to add error")
|
||||
}
|
||||
}
|
||||
203
core/plugin_registry.go
Normal file
203
core/plugin_registry.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/azure"
|
||||
"github.com/danielmiessler/fabric/plugins/tools"
|
||||
"github.com/samber/lo"
|
||||
"strconv"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"github.com/danielmiessler/fabric/plugins/ai"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/anthropic"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/dryrun"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/gemini"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/groq"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/mistral"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/ollama"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openrouter"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/siliconcloud"
|
||||
"github.com/danielmiessler/fabric/plugins/db/fsdb"
|
||||
"github.com/danielmiessler/fabric/plugins/tools/jina"
|
||||
"github.com/danielmiessler/fabric/plugins/tools/lang"
|
||||
"github.com/danielmiessler/fabric/plugins/tools/youtube"
|
||||
)
|
||||
|
||||
func NewPluginRegistry(db *fsdb.Db) (ret *PluginRegistry) {
|
||||
ret = &PluginRegistry{
|
||||
Db: db,
|
||||
VendorManager: ai.NewVendorsManager(),
|
||||
VendorsAll: ai.NewVendorsManager(),
|
||||
PatternsLoader: tools.NewPatternsLoader(db.Patterns),
|
||||
YouTube: youtube.NewYouTube(),
|
||||
Language: lang.NewLanguage(),
|
||||
Jina: jina.NewClient(),
|
||||
}
|
||||
|
||||
ret.Defaults = tools.NeeDefaults(ret.VendorManager.GetModels)
|
||||
|
||||
ret.VendorsAll.AddVendors(openai.NewClient(), ollama.NewClient(), azure.NewClient(), groq.NewClient(),
|
||||
gemini.NewClient(), anthropic.NewClient(), siliconcloud.NewClient(), openrouter.NewClient(), mistral.NewClient())
|
||||
_ = ret.Configure()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type PluginRegistry struct {
|
||||
Db *fsdb.Db
|
||||
|
||||
VendorManager *ai.VendorsManager
|
||||
VendorsAll *ai.VendorsManager
|
||||
Defaults *tools.Defaults
|
||||
PatternsLoader *tools.PatternsLoader
|
||||
YouTube *youtube.YouTube
|
||||
Language *lang.Language
|
||||
Jina *jina.Client
|
||||
}
|
||||
|
||||
func (o *PluginRegistry) SaveEnvFile() (err error) {
|
||||
// Now create the .env with all configured VendorsController info
|
||||
var envFileContent bytes.Buffer
|
||||
|
||||
o.Defaults.Settings.FillEnvFileContent(&envFileContent)
|
||||
o.PatternsLoader.SetupFillEnvFileContent(&envFileContent)
|
||||
|
||||
for _, vendor := range o.VendorManager.Vendors {
|
||||
vendor.SetupFillEnvFileContent(&envFileContent)
|
||||
}
|
||||
|
||||
o.YouTube.SetupFillEnvFileContent(&envFileContent)
|
||||
o.Jina.SetupFillEnvFileContent(&envFileContent)
|
||||
o.Language.SetupFillEnvFileContent(&envFileContent)
|
||||
|
||||
err = o.Db.SaveEnv(envFileContent.String())
|
||||
return
|
||||
}
|
||||
|
||||
func (o *PluginRegistry) Setup() (err error) {
|
||||
setupQuestion := plugins.NewSetupQuestion("Enter the number of the plugin to setup")
|
||||
groupsPlugins := common.NewGroupsItemsSelector[plugins.Plugin]("Available plugins",
|
||||
func(plugin plugins.Plugin) string {
|
||||
var configuredLabel string
|
||||
if plugin.IsConfigured() {
|
||||
configuredLabel = " (configured)"
|
||||
} else {
|
||||
configuredLabel = ""
|
||||
}
|
||||
return fmt.Sprintf("%v%v", plugin.GetSetupDescription(), configuredLabel)
|
||||
})
|
||||
|
||||
groupsPlugins.AddGroupItems("AI Vendors [at least one, required]", lo.Map(o.VendorsAll.Vendors,
|
||||
func(vendor ai.Vendor, _ int) plugins.Plugin {
|
||||
return vendor
|
||||
})...)
|
||||
|
||||
groupsPlugins.AddGroupItems("Tools", o.Defaults, o.PatternsLoader, o.YouTube, o.Language, o.Jina)
|
||||
|
||||
for {
|
||||
groupsPlugins.Print()
|
||||
|
||||
if answerErr := setupQuestion.Ask("Plugin Number"); answerErr != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if setupQuestion.Value == "" {
|
||||
break
|
||||
}
|
||||
number, parseErr := strconv.Atoi(setupQuestion.Value)
|
||||
setupQuestion.Value = ""
|
||||
|
||||
if parseErr == nil {
|
||||
var plugin plugins.Plugin
|
||||
if _, plugin, err = groupsPlugins.GetGroupAndItemByItemNumber(number); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if pluginSetupErr := plugin.Setup(); pluginSetupErr != nil {
|
||||
println(pluginSetupErr.Error())
|
||||
} else {
|
||||
if err = o.SaveEnvFile(); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := o.VendorManager.VendorsByName[plugin.GetName()]; !ok {
|
||||
if vendor, ok := plugin.(ai.Vendor); ok {
|
||||
o.VendorManager.AddVendors(vendor)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = o.SaveEnvFile()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (o *PluginRegistry) SetupVendor(vendorName string) (err error) {
|
||||
if err = o.VendorsAll.SetupVendor(vendorName, o.VendorManager.VendorsByName); err != nil {
|
||||
return
|
||||
}
|
||||
err = o.SaveEnvFile()
|
||||
return
|
||||
}
|
||||
|
||||
// Configure buildClient VendorsController based on the environment variables
|
||||
func (o *PluginRegistry) Configure() (err error) {
|
||||
for _, vendor := range o.VendorsAll.Vendors {
|
||||
if vendorErr := vendor.Configure(); vendorErr == nil {
|
||||
o.VendorManager.AddVendors(vendor)
|
||||
}
|
||||
}
|
||||
_ = o.Defaults.Configure()
|
||||
_ = o.PatternsLoader.Configure()
|
||||
|
||||
//YouTube and Jina are not mandatory, so ignore not configured error
|
||||
_ = o.YouTube.Configure()
|
||||
_ = o.Jina.Configure()
|
||||
_ = o.Language.Configure()
|
||||
return
|
||||
}
|
||||
|
||||
func (o *PluginRegistry) GetChatter(model string, stream bool, dryRun bool) (ret *Chatter, err error) {
|
||||
ret = &Chatter{
|
||||
db: o.Db,
|
||||
Stream: stream,
|
||||
DryRun: dryRun,
|
||||
}
|
||||
|
||||
defaultModel := o.Defaults.Model.Value
|
||||
defaultVendor := o.Defaults.Vendor.Value
|
||||
vendorManager := o.VendorManager
|
||||
|
||||
if dryRun {
|
||||
ret.vendor = dryrun.NewClient()
|
||||
ret.model = model
|
||||
if ret.model == "" {
|
||||
ret.model = defaultModel
|
||||
}
|
||||
} else if model == "" {
|
||||
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))
|
||||
ret.model = model
|
||||
}
|
||||
|
||||
if ret.vendor == nil {
|
||||
err = fmt.Errorf(
|
||||
"could not find vendor.\n Model = %s\n Model = %s\n Vendor = %s",
|
||||
model, defaultModel, defaultVendor)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
16
core/plugin_registry_test.go
Normal file
16
core/plugin_registry_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/plugins/db/fsdb"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSaveEnvFile(t *testing.T) {
|
||||
registry := NewPluginRegistry(fsdb.NewDb(os.TempDir()))
|
||||
|
||||
err := registry.SaveEnvFile()
|
||||
if err != nil {
|
||||
t.Fatalf("SaveEnvFile() error = %v", err)
|
||||
}
|
||||
}
|
||||
107
core/vendors.go
107
core/vendors.go
@@ -1,107 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/danielmiessler/fabric/vendors"
|
||||
)
|
||||
|
||||
func NewVendorsManager() *VendorsManager {
|
||||
return &VendorsManager{
|
||||
Vendors: map[string]vendors.Vendor{},
|
||||
}
|
||||
}
|
||||
|
||||
type VendorsManager struct {
|
||||
Vendors map[string]vendors.Vendor
|
||||
Models *VendorsModels
|
||||
}
|
||||
|
||||
func (o *VendorsManager) AddVendors(vendors ...vendors.Vendor) {
|
||||
for _, vendor := range vendors {
|
||||
o.Vendors[vendor.GetName()] = vendor
|
||||
}
|
||||
}
|
||||
|
||||
func (o *VendorsManager) GetModels() *VendorsModels {
|
||||
if o.Models == nil {
|
||||
o.readModels()
|
||||
}
|
||||
return o.Models
|
||||
}
|
||||
|
||||
func (o *VendorsManager) HasVendors() bool {
|
||||
return len(o.Vendors) > 0
|
||||
}
|
||||
|
||||
func (o *VendorsManager) FindByName(name string) vendors.Vendor {
|
||||
return o.Vendors[name]
|
||||
}
|
||||
|
||||
func (o *VendorsManager) readModels() {
|
||||
o.Models = NewVendorsModels()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
resultsChan := make(chan modelResult, len(o.Vendors))
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
for _, vendor := range o.Vendors {
|
||||
wg.Add(1)
|
||||
go o.fetchVendorModels(ctx, &wg, vendor, resultsChan)
|
||||
}
|
||||
|
||||
// Wait for all goroutines to finish
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(resultsChan)
|
||||
}()
|
||||
|
||||
// Collect results
|
||||
for result := range resultsChan {
|
||||
if result.err != nil {
|
||||
fmt.Println(result.vendorName, result.err)
|
||||
o.Models.AddError(result.err)
|
||||
cancel() // Cancel remaining goroutines if needed
|
||||
} else {
|
||||
o.Models.AddVendorModels(result.vendorName, result.models)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *VendorsManager) fetchVendorModels(
|
||||
ctx context.Context, wg *sync.WaitGroup, vendor vendors.Vendor, resultsChan chan<- modelResult) {
|
||||
|
||||
defer wg.Done()
|
||||
|
||||
models, err := vendor.ListModels()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Context canceled, don't send the result
|
||||
return
|
||||
case resultsChan <- modelResult{vendorName: vendor.GetName(), models: models, err: err}:
|
||||
// Result sent
|
||||
}
|
||||
}
|
||||
|
||||
func (o *VendorsManager) Setup() (ret map[string]vendors.Vendor, err error) {
|
||||
ret = map[string]vendors.Vendor{}
|
||||
for _, vendor := range o.Vendors {
|
||||
fmt.Println()
|
||||
if vendorErr := vendor.Setup(); vendorErr == nil {
|
||||
fmt.Printf("[%v] configured\n", vendor.GetName())
|
||||
ret[vendor.GetName()] = vendor
|
||||
} else {
|
||||
fmt.Printf("[%v] skipped\n", vendor.GetName())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type modelResult struct {
|
||||
vendorName string
|
||||
models []string
|
||||
err error
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
)
|
||||
|
||||
func TestNewVendorsManager(t *testing.T) {
|
||||
vendorsManager := NewVendorsManager()
|
||||
if vendorsManager == nil {
|
||||
t.Fatalf("NewVendorsManager() returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddVendors(t *testing.T) {
|
||||
vendorsManager := NewVendorsManager()
|
||||
mockVendor := &MockVendor{name: "testVendor"}
|
||||
vendorsManager.AddVendors(mockVendor)
|
||||
|
||||
if _, exists := vendorsManager.Vendors[mockVendor.GetName()]; !exists {
|
||||
t.Fatalf("AddVendors() did not add vendor")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetModels(t *testing.T) {
|
||||
vendorsManager := NewVendorsManager()
|
||||
mockVendor := &MockVendor{name: "testVendor"}
|
||||
vendorsManager.AddVendors(mockVendor)
|
||||
|
||||
models := vendorsManager.GetModels()
|
||||
if models == nil {
|
||||
t.Fatalf("GetModels() returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasVendors(t *testing.T) {
|
||||
vendorsManager := NewVendorsManager()
|
||||
if vendorsManager.HasVendors() {
|
||||
t.Fatalf("HasVendors() should return false for an empty manager")
|
||||
}
|
||||
|
||||
mockVendor := &MockVendor{name: "testVendor"}
|
||||
vendorsManager.AddVendors(mockVendor)
|
||||
if !vendorsManager.HasVendors() {
|
||||
t.Fatalf("HasVendors() should return true after adding a vendor")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindByName(t *testing.T) {
|
||||
vendorsManager := NewVendorsManager()
|
||||
mockVendor := &MockVendor{name: "testVendor"}
|
||||
vendorsManager.AddVendors(mockVendor)
|
||||
|
||||
foundVendor := vendorsManager.FindByName("testVendor")
|
||||
if foundVendor == nil {
|
||||
t.Fatalf("FindByName() did not find added vendor")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadModels(t *testing.T) {
|
||||
vendorsManager := NewVendorsManager()
|
||||
mockVendor := &MockVendor{name: "testVendor"}
|
||||
vendorsManager.AddVendors(mockVendor)
|
||||
|
||||
vendorsManager.readModels()
|
||||
if vendorsManager.Models == nil || len(vendorsManager.Models.Vendors) == 0 {
|
||||
t.Fatalf("readModels() did not read models correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
vendorsManager := NewVendorsManager()
|
||||
mockVendor := &MockVendor{name: "testVendor"}
|
||||
vendorsManager.AddVendors(mockVendor)
|
||||
|
||||
vendors, err := vendorsManager.Setup()
|
||||
if err != nil {
|
||||
t.Fatalf("Setup() error = %v", err)
|
||||
}
|
||||
if len(vendors) == 0 {
|
||||
t.Fatalf("Setup() did not setup any vendors")
|
||||
}
|
||||
}
|
||||
|
||||
// MockVendor is a mock implementation of the Vendor interface for testing purposes.
|
||||
type MockVendor struct {
|
||||
*common.Settings
|
||||
name string
|
||||
}
|
||||
|
||||
func (o *MockVendor) SendStream(messages []*common.Message, options *common.ChatOptions, strings chan string) error {
|
||||
// TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (o *MockVendor) Send(ctx context.Context, messages []*common.Message, options *common.ChatOptions) (string, error) {
|
||||
// TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (o *MockVendor) SetupFillEnvFileContent(buffer *bytes.Buffer) {
|
||||
// TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (o *MockVendor) IsConfigured() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *MockVendor) GetSettings() *common.Settings {
|
||||
return o.Settings
|
||||
}
|
||||
|
||||
func (o *MockVendor) GetName() string {
|
||||
return o.name
|
||||
}
|
||||
|
||||
func (o *MockVendor) Configure() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *MockVendor) Setup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *MockVendor) ListModels() ([]string, error) {
|
||||
return []string{"model1", "model2"}, nil
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package db
|
||||
|
||||
type Contexts struct {
|
||||
*Storage
|
||||
}
|
||||
|
||||
// GetContext Load a context from file
|
||||
func (o *Contexts) GetContext(name string) (ret *Context, err error) {
|
||||
var content []byte
|
||||
if content, err = o.Load(name); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ret = &Context{Name: name, Content: string(content)}
|
||||
return
|
||||
}
|
||||
|
||||
type Context struct {
|
||||
Name string
|
||||
Content string
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package db
|
||||
@@ -1,38 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
)
|
||||
|
||||
type Sessions struct {
|
||||
*Storage
|
||||
}
|
||||
|
||||
func (o *Sessions) GetOrCreateSession(name string) (session *Session, err error) {
|
||||
session = &Session{Name: name}
|
||||
|
||||
if o.Exists(name) {
|
||||
err = o.LoadAsJson(name, &session.Messages)
|
||||
} else {
|
||||
fmt.Printf("Creating new session: %s\n", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Sessions) SaveSession(session *Session) (err error) {
|
||||
return o.SaveAsJson(session.Name, session.Messages)
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
Name string
|
||||
Messages []*common.Message
|
||||
}
|
||||
|
||||
func (o *Session) IsEmpty() bool {
|
||||
return len(o.Messages) == 0
|
||||
}
|
||||
|
||||
func (o *Session) Append(messages ...*common.Message) {
|
||||
o.Messages = append(o.Messages, messages...)
|
||||
}
|
||||
92
go.mod
92
go.mod
@@ -2,74 +2,96 @@ module github.com/danielmiessler/fabric
|
||||
|
||||
go 1.22.5
|
||||
|
||||
toolchain go1.22.6
|
||||
|
||||
require (
|
||||
github.com/anaskhan96/soup v1.2.5
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/go-git/go-git/v5 v5.12.0
|
||||
github.com/google/generative-ai-go v0.17.0
|
||||
github.com/go-shiori/go-readability v0.0.0-20240923125239-59a7bd165825
|
||||
github.com/google/generative-ai-go v0.18.0
|
||||
github.com/jessevdk/go-flags v1.6.1
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/liushuangls/go-anthropic/v2 v2.6.0
|
||||
github.com/ollama/ollama v0.3.6
|
||||
github.com/liushuangls/go-anthropic/v2 v2.8.0
|
||||
github.com/ollama/ollama v0.3.11
|
||||
github.com/otiai10/copy v1.14.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/samber/lo v1.47.0
|
||||
github.com/sashabaranov/go-openai v1.28.2
|
||||
github.com/sashabaranov/go-openai v1.30.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
google.golang.org/api v0.192.0
|
||||
golang.org/x/text v0.19.0
|
||||
google.golang.org/api v0.197.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.115.0 // indirect
|
||||
cloud.google.com/go/ai v0.8.0 // indirect
|
||||
cloud.google.com/go/auth v0.8.1 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.5.0 // indirect
|
||||
cloud.google.com/go/longrunning v0.5.7 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
cloud.google.com/go v0.115.1 // indirect
|
||||
cloud.google.com/go/ai v0.8.2 // indirect
|
||||
cloud.google.com/go/auth v0.9.4 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.5.1 // indirect
|
||||
cloud.google.com/go/longrunning v0.6.1 // indirect
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||
github.com/anaskhan96/soup v1.2.5 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cloudflare/circl v1.4.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.3.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/s2a-go v0.1.8 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/skeema/knownhosts v1.2.2 // indirect
|
||||
github.com/skeema/knownhosts v1.3.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
|
||||
go.opentelemetry.io/otel v1.26.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.26.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.26.0 // indirect
|
||||
golang.org/x/crypto v0.25.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/oauth2 v0.22.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect
|
||||
go.opentelemetry.io/otel v1.30.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.30.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.30.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/net v0.30.0 // indirect
|
||||
golang.org/x/oauth2 v0.23.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/time v0.6.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect
|
||||
google.golang.org/grpc v1.64.1 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/grpc v1.66.2 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
202
go.sum
202
go.sum
@@ -1,41 +1,53 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
|
||||
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
|
||||
cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w=
|
||||
cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE=
|
||||
cloud.google.com/go/auth v0.8.1 h1:QZW9FjC5lZzN864p13YxvAtGUlQ+KgRL+8Sg45Z6vxo=
|
||||
cloud.google.com/go/auth v0.8.1/go.mod h1:qGVp/Y3kDRSDZ5gFD/XPUfYQ9xW1iI7q8RIRoCyBbJc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I=
|
||||
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
|
||||
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
||||
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
|
||||
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ=
|
||||
cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc=
|
||||
cloud.google.com/go/ai v0.8.2 h1:LEaQwqBv+k2ybrcdTtCTc9OPZXoEdcQaGrfvDYS6Bnk=
|
||||
cloud.google.com/go/ai v0.8.2/go.mod h1:Wb3EUUGWwB6yHBaUf/+oxUq/6XbCaU1yh0GrwUS8lr4=
|
||||
cloud.google.com/go/auth v0.9.4 h1:DxF7imbEbiFu9+zdKC6cKBko1e8XeJnipNqIbWZ+kDI=
|
||||
cloud.google.com/go/auth v0.9.4/go.mod h1:SHia8n6//Ya940F1rLimhJCjjx7KE17t0ctFEci3HkA=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
|
||||
cloud.google.com/go/compute/metadata v0.5.1 h1:NM6oZeZNlYjiwYje+sYFjEpP0Q0zCan1bmQW/KmIrGs=
|
||||
cloud.google.com/go/compute/metadata v0.5.1/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
|
||||
cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc=
|
||||
cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/anaskhan96/soup v1.2.5 h1:V/FHiusdTrPrdF4iA1YkVxsOpdNcgvqT1hG+YtcZ5hM=
|
||||
github.com/anaskhan96/soup v1.2.5/go.mod h1:6YnEp9A2yywlYdM4EgDz9NEHclocMepEtku7wg6Cq3s=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
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/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=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/cloudflare/circl v1.4.0 h1:BV7h5MgrktNzytKmWjpOtdYrf0lkkbF8YMlBGPhJQrY=
|
||||
github.com/cloudflare/circl v1.4.0/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/cyphar/filepath-securejoin v0.3.2 h1:QhZu5AxQ+o1XZH0Ye05YzvJ0kAdK6VQc0z9NNMek7gc=
|
||||
github.com/cyphar/filepath-securejoin v0.3.2/go.mod h1:F7i41x/9cBF7lzCrVsYs9fuzwRZm4NQsGTBdpp6mETc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -49,6 +61,12 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
@@ -64,6 +82,22 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c h1:wpkoddUomPfHiOziHZixGO5ZBS73cKqVzZipfrLmO1w=
|
||||
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c/go.mod h1:oVDCh3qjJMLVUSILBRwrm+Bc6RNXGZYtoh9xdvf1ffM=
|
||||
github.com/go-shiori/go-readability v0.0.0-20240923125239-59a7bd165825 h1:CpSi7xiWqGaAqVn/2MsbRoDmPwXMvvQUu3hLjX1QrOM=
|
||||
github.com/go-shiori/go-readability v0.0.0-20240923125239-59a7bd165825/go.mod h1:YWa00ashoPZMAOElrSn4E1cJErhDVU6PWAll4Hxzn+w=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
@@ -80,8 +114,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
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.17.0 h1:kUmCXUIwJouD7I7ev3OmxzzQVICyhIWAxaXk2yblCMY=
|
||||
github.com/google/generative-ai-go v0.17.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E=
|
||||
github.com/google/generative-ai-go v0.18.0 h1:6ybg9vOCLcI/UpBBYXOTVgvKmcUKFRNj+2Cj3GnebSo=
|
||||
github.com/google/generative-ai-go v0.18.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@@ -90,13 +124,14 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
|
||||
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
|
||||
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
|
||||
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
@@ -105,8 +140,14 @@ github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bB
|
||||
github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc=
|
||||
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/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=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@@ -114,16 +155,28 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/liushuangls/go-anthropic/v2 v2.6.0 h1:hkgLQPD04wL4lFrV5ZoGlIyy4f6P+brIuRlzn2S8K9s=
|
||||
github.com/liushuangls/go-anthropic/v2 v2.6.0/go.mod h1:8BKv/fkeTaL5R9R9bGkaknYBueyw2WxY20o7bImbOek=
|
||||
github.com/ollama/ollama v0.3.6 h1:nA/N0AmjP327po5cZDGLqI40nl+aeei0pD0dLa92ypE=
|
||||
github.com/ollama/ollama v0.3.6/go.mod h1:YrWoNkFnPOYsnDvsf/Ztb1wxU9/IXrNsQHqcxbY2r94=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/liushuangls/go-anthropic/v2 v2.8.0 h1:0zH2jDNycbrlszxnLrG+Gx8vVT0yJAPWU4s3ZTkWzgI=
|
||||
github.com/liushuangls/go-anthropic/v2 v2.8.0/go.mod h1:8BKv/fkeTaL5R9R9bGkaknYBueyw2WxY20o7bImbOek=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
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.3.11 h1:Fs1B5WjXYUvr5bkMZZpUJfiqIAxrymujRidFABwMeV8=
|
||||
github.com/ollama/ollama v0.3.11/go.mod h1:YrWoNkFnPOYsnDvsf/Ztb1wxU9/IXrNsQHqcxbY2r94=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
|
||||
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
|
||||
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
|
||||
github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -131,59 +184,70 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
github.com/sashabaranov/go-openai v1.28.2 h1:Q3pi34SuNYNN7YrqpHlHbpeYlf75ljgHOAVM/r1yun0=
|
||||
github.com/sashabaranov/go-openai v1.28.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sashabaranov/go-openai v1.30.0 h1:fHv9urGxABfm885xGWsXFSk5cksa+8dJ4jGli/UQQcI=
|
||||
github.com/sashabaranov/go-openai v1.30.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
|
||||
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
||||
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
|
||||
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc=
|
||||
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
|
||||
go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
|
||||
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
|
||||
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
|
||||
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
|
||||
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 h1:hCq2hNMwsegUvPzI7sPOvtO9cqyy5GbWt/Ybp2xrx8Q=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0/go.mod h1:LqaApwGx/oUmzsbqxkzuBvyoPpkxk3JQWnqfVrJ3wCA=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI=
|
||||
go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
|
||||
go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
|
||||
go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w=
|
||||
go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ=
|
||||
go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc=
|
||||
go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -198,11 +262,12 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -226,15 +291,17 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
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=
|
||||
@@ -242,8 +309,9 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -254,28 +322,26 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
|
||||
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=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.192.0 h1:PljqpNAfZaaSpS+TnANfnNAXKdzHM/B9bKhwRlo7JP0=
|
||||
google.golang.org/api v0.192.0/go.mod h1:9VcphjvAxPKLmSxVSzPlSRXy/5ARMEw5bf58WoVXafQ=
|
||||
google.golang.org/api v0.197.0 h1:x6CwqQLsFiA5JKAiGyGBjc2bNtHtLddhJCE2IKuhhcQ=
|
||||
google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf h1:liao9UHurZLtiEwBgT9LMOnKYsHze6eA6w1KQCMVN2Q=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
|
||||
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
|
||||
google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
|
||||
google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -291,8 +357,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/gookit/color.v1 v1.1.6 h1:5fB10p6AUFjhd2ayq9JgmJWr9WlTrguFdw3qlYtKNHk=
|
||||
gopkg.in/gookit/color.v1 v1.1.6/go.mod h1:IcEkFGaveVShJ+j8ew+jwe9epHyGpJ9IrptHmW3laVY=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@@ -302,3 +366,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
5
main.go
5
main.go
@@ -2,14 +2,15 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jessevdk/go-flags"
|
||||
"os"
|
||||
|
||||
"github.com/danielmiessler/fabric/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
_, err := cli.Cli()
|
||||
if err != nil {
|
||||
err := cli.Cli(version)
|
||||
if err != nil && !flags.WroteHelp(err) {
|
||||
fmt.Printf("%s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -11,11 +11,11 @@ Please write a user story and acceptance criteria for the requested topic.
|
||||
Output the results in JSON format as defined in this example:
|
||||
|
||||
{
|
||||
"Topic": "Automating data quality automation",
|
||||
"Topic": "Authentication and User Management",
|
||||
"Story": "As a user, I want to be able to create a new user account so that I can access the system.",
|
||||
"Criteria": "Given that I am a user, when I click the 'Create Account' button, then I should be prompted to enter my email address, password, and confirm password. When I click the 'Submit' button, then I should be redirected to the login page."
|
||||
}
|
||||
|
||||
# INPUT:
|
||||
|
||||
INPUT:
|
||||
INPUT:
|
||||
|
||||
29
patterns/analyze_military_strategy/system.md
Normal file
29
patterns/analyze_military_strategy/system.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# IDENTITY and PURPOSE
|
||||
You are a military historian and strategic analyst specializing in dissecting historical battles. Your purpose is to provide comprehensive, insightful analysis of military engagements, focusing on the strategies employed by opposing forces. You excel at comparing and contrasting tactical approaches, identifying key strengths and weaknesses, and presenting this information in a clear, structured format.
|
||||
|
||||
# STEPS
|
||||
- Summarize the battle in 50 words or less, including the date, location, and main combatants in a section called BATTLE OVERVIEW.
|
||||
- Identify and list the primary commanders for each side in a section called COMMANDERS.
|
||||
- Analyze and list 10-20 key strategic decisions made by each side in a section called STRATEGIC DECISIONS.
|
||||
- Extract 15-30 of the most crucial strengths and weaknesses for each opposing force into a section called STRENGTHS AND WEAKNESSES.
|
||||
- Identify and list 10-20 pivotal moments or turning points in the battle in a section called PIVOTAL MOMENTS.
|
||||
- Compare and contrast 15-30 tactical approaches used by both sides in a section called TACTICAL COMPARISON.
|
||||
- Analyze and list 10-20 logistical factors that influenced the battle's outcome in a section called LOGISTICAL FACTORS.
|
||||
- Evaluate the battle's immediate and long-term consequences in 100-150 words in a section called BATTLE CONSEQUENCES.
|
||||
- Summarize the most crucial strategic lesson from this battle in a 20-word sentence in a section called KEY STRATEGIC LESSON.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
- Only output in Markdown format.
|
||||
- Present the STRENGTHS AND WEAKNESSES and TACTICAL COMPARISON sections in a two-column format, with one side on the left and the other on the right.
|
||||
- Write the STRATEGIC DECISIONS bullets as exactly 20 words each.
|
||||
- Write the PIVOTAL MOMENTS bullets as exactly 15 words each.
|
||||
- Write the LOGISTICAL FACTORS bullets as exactly 15 words each.
|
||||
- Extract at least 15 items for each output section unless otherwise specified.
|
||||
- Do not give warnings or notes; only output the requested sections.
|
||||
- Use bulleted lists for output, not numbered lists.
|
||||
- Do not repeat information across different sections.
|
||||
- Ensure variety in how bullet points begin; avoid repetitive phrasing.
|
||||
- Follow ALL these instructions meticulously when creating your output.
|
||||
|
||||
# INPUT
|
||||
INPUT:
|
||||
39
patterns/extract_core_message/system.md
Normal file
39
patterns/extract_core_message/system.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# IDENTITY
|
||||
|
||||
You are an expert at looking at a presentation, an essay, or a full body of lifetime work, and clearly and accurately articulating what the core message is.
|
||||
|
||||
# GOAL
|
||||
|
||||
- Produce a clear sentence that perfectly articulates the core message as presented in a given text or body of work.
|
||||
|
||||
# EXAMPLE
|
||||
|
||||
If the input is all of Victor Frankl's work, then the core message would be:
|
||||
|
||||
Finding meaning in suffering is key to human resilience, purpose, and enduring life’s challenges.
|
||||
|
||||
END EXAMPLE
|
||||
|
||||
# STEPS
|
||||
|
||||
- Fully digest the input.
|
||||
|
||||
- Determine if the input is a single text or a body of work.
|
||||
|
||||
- Based on which it is, parse the thing that's supposed to be parsed.
|
||||
|
||||
- Extract the core message from the parsed text into a single sentence.
|
||||
|
||||
# OUTPUT
|
||||
|
||||
- Output a single, 15-word sentence that perfectly articulates the core message as presented in the input.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
- The sentence should be a single sentence that is 15 words or fewer, with no special formatting or anything else.
|
||||
|
||||
- Do not include any setup to the sentence, e.g., "The core message is to…", etc. Just list the core message and nothing else.
|
||||
|
||||
- ONLY OUTPUT THE CORE MESSAGE, not a setup to it, commentary on it, or anything else.
|
||||
|
||||
- Do not ask questions or complain in any way about the task.
|
||||
23
patterns/extract_latest_video/system.md
Normal file
23
patterns/extract_latest_video/system.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# IDENTITY and PURPOSE
|
||||
|
||||
You are an expert at extracting the latest video URL from a YouTube RSS feed.
|
||||
|
||||
# Steps
|
||||
|
||||
- Read the full RSS feed.
|
||||
|
||||
- Find the latest posted video URL.
|
||||
|
||||
- Output the full video URL and nothing else.
|
||||
|
||||
# EXAMPLE OUTPUT
|
||||
|
||||
https://www.youtube.com/watch?v=abc123
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
- Do not output warnings or notes—just the requested sections.
|
||||
|
||||
# INPUT:
|
||||
|
||||
INPUT:
|
||||
37
patterns/extract_most_redeeming_thing/system.md
Normal file
37
patterns/extract_most_redeeming_thing/system.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# IDENTITY
|
||||
|
||||
You are an expert at looking at an input and extracting the most redeeming thing about them, even if they're mostly horrible.
|
||||
|
||||
# GOAL
|
||||
|
||||
- Produce the most redeeming thing about the thing given in input.
|
||||
|
||||
# EXAMPLE
|
||||
|
||||
If the body of work is all of Ted Kazcynski's writings, then the most redeeming thing him would be:
|
||||
|
||||
He really stuck to his convictions by living in a cabin in the woods.
|
||||
|
||||
END EXAMPLE
|
||||
|
||||
# STEPS
|
||||
|
||||
- Fully digest the input.
|
||||
|
||||
- Determine if the input is a single text or a body of work.
|
||||
|
||||
- Based on which it is, parse the thing that's supposed to be parsed.
|
||||
|
||||
- Extract the most redeeming thing with the world from the parsed text into a single sentence.
|
||||
|
||||
# OUTPUT
|
||||
|
||||
- Output a single, 15-word sentence that perfectly articulates the most redeeming thing with the world as presented in the input.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
- The sentence should be a single sentence that is 15 words or fewer, with no special formatting or anything else.
|
||||
|
||||
- Do not include any setup to the sentence, e.g., "The most redeeming thing…", etc. Just list the redeeming thing and nothing else.
|
||||
|
||||
- Do not ask questions or complain in any way about the task.
|
||||
@@ -6,7 +6,7 @@ Take a step back and think step-by-step about how to achieve the best possible r
|
||||
|
||||
# STEPS
|
||||
|
||||
- Extract all predictions made within the content.
|
||||
- Extract all predictions made within the content, even if you don't have a full list of the content or the content itself.
|
||||
|
||||
- For each prediction, extract the following:
|
||||
|
||||
|
||||
39
patterns/extract_primary_solution/system.md
Normal file
39
patterns/extract_primary_solution/system.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# IDENTITY
|
||||
|
||||
You are an expert at looking at a presentation, an essay, or a full body of lifetime work, and clearly and accurately articulating what the author(s) believe is the primary solution for the world.
|
||||
|
||||
# GOAL
|
||||
|
||||
- Produce a clear sentence that perfectly articulates the primary solution with the world as presented in a given text or body of work.
|
||||
|
||||
# EXAMPLE
|
||||
|
||||
If the body of work is all of Ted Kazcynski's writings, then the primary solution with the world would be:
|
||||
|
||||
Reject all technology and return to a natural, pre-technological state of living.
|
||||
|
||||
END EXAMPLE
|
||||
|
||||
# STEPS
|
||||
|
||||
- Fully digest the input.
|
||||
|
||||
- Determine if the input is a single text or a body of work.
|
||||
|
||||
- Based on which it is, parse the thing that's supposed to be parsed.
|
||||
|
||||
- Extract the primary solution with the world from the parsed text into a single sentence.
|
||||
|
||||
# OUTPUT
|
||||
|
||||
- Output a single, 15-word sentence that perfectly articulates the primary solution with the world as presented in the input.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
- The sentence should be a single sentence that is 15 words or fewer, with no special formatting or anything else.
|
||||
|
||||
- Do not include any setup to the sentence, e.g., "The solution according to…", etc. Just list the problem and nothing else.
|
||||
|
||||
- ONLY OUTPUT THE SOLUTION, not a setup to the solution. Or a description of the solution. Just the solution.
|
||||
|
||||
- Do not ask questions or complain in any way about the task.
|
||||
31
patterns/extract_product_features/system.md
Normal file
31
patterns/extract_product_features/system.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# IDENTITY and PURPOSE
|
||||
|
||||
You extract the list of product features from the input.
|
||||
|
||||
Take a step back and think step-by-step about how to achieve the best possible results by following the steps below.
|
||||
|
||||
# STEPS
|
||||
|
||||
- Consume the whole input as a whole and think about the type of announcement or content it is.
|
||||
|
||||
- Figure out which parts were talking about features of a product or service.
|
||||
|
||||
- Output the list of features as a bulleted list of 15 words per bullet.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
- Only output Markdown.
|
||||
|
||||
- Do not give warnings or notes; only output the requested sections.
|
||||
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
|
||||
- Do not start items with the same opening words.
|
||||
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
# INPUT
|
||||
|
||||
INPUT:
|
||||
@@ -5,7 +5,9 @@ You are an expert at extracting the sponsors and potential sponsors from a given
|
||||
# Steps
|
||||
|
||||
- Consume the whole transcript so you understand what is content, what is meta information, etc.
|
||||
|
||||
- Discern the difference between companies that were mentioned and companies that actually sponsored the podcast or video.
|
||||
|
||||
- Output the following:
|
||||
|
||||
## OFFICIAL SPONSORS
|
||||
@@ -15,36 +17,20 @@ You are an expert at extracting the sponsors and potential sponsors from a given
|
||||
- $SOURCE_CHANNEL$ | $SPONSOR3$ | $SPONSOR3_DESCRIPTION$ | $SPONSOR3_LINK$
|
||||
- And so on…
|
||||
|
||||
## POTENTIAL SPONSORS
|
||||
|
||||
- $SOURCE_CHANNEL$ | $SPONSOR1$ | $SPONSOR1_DESCRIPTION$ | $SPONSOR1_LINK$
|
||||
- $SOURCE_CHANNEL$ | $SPONSOR2$ | $SPONSOR2_DESCRIPTION$ | $SPONSOR2_LINK$
|
||||
- $SOURCE_CHANNEL$ | $SPONSOR3$ | $SPONSOR3_DESCRIPTION$ | $SPONSOR3_LINK$
|
||||
- And so on…
|
||||
|
||||
# EXAMPLE OUTPUT
|
||||
|
||||
## OFFICIAL SPONSORS
|
||||
|
||||
- AI Jason's YouTube Channel | Flair | Flair is a threat intel platform powered by AI. | https://flair.ai
|
||||
- Matthew Berman's YouTube Channel | Weaviate | Weviate is an open-source knowledge graph powered by ML. | https://weaviate.com
|
||||
- Unsupervised Learning Website | JunaAI | JunaAI is a platform for AI-powered content creation. | https://junaai.com
|
||||
- The AI Junkie Podcast | JunaAI | JunaAI is a platform for AI-powered content creation. | https://junaai.com
|
||||
|
||||
## POTENTIAL SPONSORS
|
||||
|
||||
- AI Jason's YouTube Channel | Flair | Flair is a threat intel platform powered by AI. | https://flair.ai
|
||||
- Matthew Berman's YouTube Channel | Weaviate | Weviate is an open-source knowledge graph powered by ML. | https://weaviate.com
|
||||
- Unsupervised Learning Website | JunaAI | JunaAI is a platform for AI-powered content creation. | https://junaai.com
|
||||
- The AI Junkie Podcast | JunaAI | JunaAI is a platform for AI-powered content creation. | https://junaai.com
|
||||
- Flair | Flair is a threat intel platform powered by AI. | https://flair.ai
|
||||
- Weaviate | Weviate is an open-source knowledge graph powered by ML. | https://weaviate.com
|
||||
- JunaAI | JunaAI is a platform for AI-powered content creation. | https://junaai.com
|
||||
- JunaAI | JunaAI is a platform for AI-powered content creation. | https://junaai.com
|
||||
|
||||
## END EXAMPLE OUTPUT
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
- The official sponsor list should only include companies that officially sponsored the content in question.
|
||||
- The potential sponsor list should include companies that were mentioned during the content but that didn't officially sponsor.
|
||||
- Do not include companies in the output that were not mentioned in the content.
|
||||
- Do not output warnings or notes—just the requested sections.
|
||||
|
||||
# INPUT:
|
||||
|
||||
27
patterns/raycast/capture_thinkers_work
Executable file
27
patterns/raycast/capture_thinkers_work
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Required parameters:
|
||||
# @raycast.schemaVersion 1
|
||||
# @raycast.title Capture Thinkers Work
|
||||
# @raycast.mode fullOutput
|
||||
|
||||
# Optional parameters:
|
||||
# @raycast.icon 🧠
|
||||
# @raycast.argument1 { "type": "text", "placeholder": "Input text", "optional": false, "percentEncoded": true}
|
||||
|
||||
# Documentation:
|
||||
# @raycast.description Run fabric capture_thinkers_work on the input text
|
||||
# @raycast.author Daniel Miessler
|
||||
# @raycast.authorURL https://github.com/danielmiessler
|
||||
|
||||
# Set PATH to include common locations and $HOME/go/bin
|
||||
PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$HOME/go/bin:$PATH"
|
||||
|
||||
# Use the PATH to find and execute fabric
|
||||
if command -v fabric >/dev/null 2>&1; then
|
||||
fabric -sp capture_thinkers_work "${1}"
|
||||
else
|
||||
echo "Error: fabric command not found in PATH"
|
||||
echo "Current PATH: $PATH"
|
||||
exit 1
|
||||
fi
|
||||
27
patterns/raycast/create_story_explanation
Executable file
27
patterns/raycast/create_story_explanation
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Required parameters:
|
||||
# @raycast.schemaVersion 1
|
||||
# @raycast.title Create Story Explanation
|
||||
# @raycast.mode fullOutput
|
||||
|
||||
# Optional parameters:
|
||||
# @raycast.icon 🧠
|
||||
# @raycast.argument1 { "type": "text", "placeholder": "Input text", "optional": false, "percentEncoded": true}
|
||||
|
||||
# Documentation:
|
||||
# @raycast.description Run fabric create_story_explanation on the input text
|
||||
# @raycast.author Daniel Miessler
|
||||
# @raycast.authorURL https://github.com/danielmiessler
|
||||
|
||||
# Set PATH to include common locations and $HOME/go/bin
|
||||
PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$HOME/go/bin:$PATH"
|
||||
|
||||
# Use the PATH to find and execute fabric
|
||||
if command -v fabric >/dev/null 2>&1; then
|
||||
fabric -sp create_story_explanation "${1}"
|
||||
else
|
||||
echo "Error: fabric command not found in PATH"
|
||||
echo "Current PATH: $PATH"
|
||||
exit 1
|
||||
fi
|
||||
27
patterns/raycast/extract_primary_problem
Executable file
27
patterns/raycast/extract_primary_problem
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Required parameters:
|
||||
# @raycast.schemaVersion 1
|
||||
# @raycast.title Extract Primary Problem
|
||||
# @raycast.mode fullOutput
|
||||
|
||||
# Optional parameters:
|
||||
# @raycast.icon 🧠
|
||||
# @raycast.argument1 { "type": "text", "placeholder": "Input text", "optional": false, "percentEncoded": true}
|
||||
|
||||
# Documentation:
|
||||
# @raycast.description Run fabric extract_primary_problem on the input text
|
||||
# @raycast.author Daniel Miessler
|
||||
# @raycast.authorURL https://github.com/danielmiessler
|
||||
|
||||
# Set PATH to include common locations and $HOME/go/bin
|
||||
PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$HOME/go/bin:$PATH"
|
||||
|
||||
# Use the PATH to find and execute fabric
|
||||
if command -v fabric >/dev/null 2>&1; then
|
||||
fabric -sp extract_primary_problem "${1}"
|
||||
else
|
||||
echo "Error: fabric command not found in PATH"
|
||||
echo "Current PATH: $PATH"
|
||||
exit 1
|
||||
fi
|
||||
61
patterns/review_design/system.md
Normal file
61
patterns/review_design/system.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# IDENTITY and PURPOSE
|
||||
|
||||
You are an expert solution architect.
|
||||
|
||||
You fully digest input and review design.
|
||||
|
||||
Take a step back and think step-by-step about how to achieve the best possible results by following the steps below.
|
||||
|
||||
# STEPS
|
||||
|
||||
Conduct a detailed review of the architecture design. Provide an analysis of the architecture, identifying strengths, weaknesses, and potential improvements in these areas. Specifically, evaluate the following:
|
||||
|
||||
1. **Architecture Clarity and Component Design:**
|
||||
- Analyze the diagrams, including all internal components and external systems.
|
||||
- Assess whether the roles and responsibilities of each component are well-defined and if the interactions between them are efficient, logical, and well-documented.
|
||||
- Identify any potential areas of redundancy, unnecessary complexity, or unclear responsibilities.
|
||||
|
||||
2. **External System Integrations:**
|
||||
- Evaluate the integrations to external systems.
|
||||
- Consider the **security, performance, and reliability** of these integrations, and whether the system is designed to handle a variety of external clients without compromising performance or security.
|
||||
|
||||
3. **Security Architecture:**
|
||||
- Assess the security mechanisms in place.
|
||||
- Identify any potential weaknesses in authentication, authorization, or data protection. Consider whether the design follows best practices.
|
||||
- Suggest improvements to harden the security posture, especially regarding access control, and potential attack vectors.
|
||||
|
||||
4. **Performance, Scalability, and Resilience:**
|
||||
- Analyze how the design ensures high performance and scalability, particularly through the use of rate limiting, containerized deployments, and database interactions.
|
||||
- Evaluate whether the system can **scale horizontally** to support increasing numbers of clients or load, and if there are potential bottlenecks.
|
||||
- Assess fault tolerance and resilience. Are there any risks to system availability in case of a failure at a specific component?
|
||||
|
||||
5. **Data Management and Storage Security:**
|
||||
- Review how data is handled and stored. Are these data stores designed to securely manage information?
|
||||
- Assess if the **data flow** between components is optimized and secure. Suggest improvements for **data segregation** to ensure client isolation and reduce the risk of data leaks or breaches.
|
||||
|
||||
6. **Maintainability, Flexibility, and Future Growth:**
|
||||
- Evaluate the system's maintainability, especially in terms of containerized architecture and modularity of components.
|
||||
- Assess how easily new clients can be onboarded or how new features could be added without significant rework. Is the design flexible enough to adapt to evolving business needs?
|
||||
- Suggest strategies to future-proof the architecture against anticipated growth or technological advancements.
|
||||
|
||||
7. **Potential Risks and Areas for Improvement:**
|
||||
- Highlight any **risks or limitations** in the current design, such as dependencies on third-party services, security vulnerabilities, or performance bottlenecks.
|
||||
- Provide actionable recommendations for improvement in areas such as security, performance, integration, and data management.
|
||||
|
||||
8. **Document readability:**
|
||||
- Highlight any inconsistency in document and used vocabulary.
|
||||
- Suggest parts that need rewrite.
|
||||
|
||||
Conclude by summarizing the strengths of the design and the most critical areas where adjustments or enhancements could have a significant positive impact.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
- Only output valid Markdown with no bold or italics.
|
||||
|
||||
- Do not give warnings or notes; only output the requested sections.
|
||||
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
# INPUT
|
||||
|
||||
INPUT:
|
||||
@@ -1,24 +1,35 @@
|
||||
# IDENTITY
|
||||
|
||||
You are an AI assistant designed to provide detailed, step-by-step responses.
|
||||
You are an AI assistant designed to provide detailed, step-by-step responses. Your outputs should follow this structure:
|
||||
|
||||
# STEPS
|
||||
|
||||
1. Begin with a <thinking> section.
|
||||
|
||||
2. Inside the thinking section:
|
||||
a. Briefly analyze the question and outline your approach.
|
||||
b. Present a clear plan of steps to solve the problem.
|
||||
c. Use a "Chain of Thought" reasoning process if necessary, breaking down y
|
||||
3. Include a reflection> section for each idea where you:
|
||||
a. Review your reasoning.
|
||||
b. Check for potential errors or oversights.
|
||||
c. Confirm or adjust your conclusion if necessary.
|
||||
4. Be sure to close all reflection sections.
|
||||
5. Close the thinking section with </thinking>.
|
||||
6. Provide your final answer in an ‹output> section.
|
||||
Always use these tags in your responses. Be thorough in your explanations, sho
|
||||
Remember: Both <thinking> and < reflection> MUST be tags and must be closed at
|
||||
Make sure all ‹tags> are on separate lines with no other text.
|
||||
|
||||
- a. Briefly analyze the question and outline your approach.
|
||||
|
||||
- b. Present a clear plan of steps to solve the problem.
|
||||
|
||||
- c. Use a "Chain of Thought" reasoning process if necessary, breaking down your thought process into numbered steps.
|
||||
|
||||
3. Include a <reflection> section for each idea where you:
|
||||
|
||||
- a. Review your reasoning.
|
||||
|
||||
- b. Check for potential errors or oversights.
|
||||
|
||||
- c. Confirm or adjust your conclusion if necessary.
|
||||
- Be sure to close all reflection sections.
|
||||
- Close the thinking section with </thinking>.
|
||||
- Provide your final answer in an <output> section.
|
||||
|
||||
Always use these tags in your responses. Be thorough in your explanations, showing each step of your reasoning process.
|
||||
Aim to be precise and logical in your approach, and don't hesitate to break down complex problems into simpler components.
|
||||
Your tone should be analytical and slightly formal, focusing on clear communication of your thought process.
|
||||
Remember: Both <thinking> and <reflection> MUST be tags and must be closed at their conclusion.
|
||||
Make sure all <tags> are on separate lines with no other text.
|
||||
|
||||
# INPUT
|
||||
|
||||
|
||||
@@ -8,32 +8,32 @@ Take a step back and think step-by-step about how to achieve the best possible r
|
||||
|
||||
- Fully digest the content provided.
|
||||
|
||||
- Extract all actionables agreed within the meeting.
|
||||
- Extract all actionables agreed upon within the meeting.
|
||||
|
||||
- Extract any interesting ideas brought up in the meeting.
|
||||
|
||||
- In a section called TITLE, write a 1 to 5 word title for the meeting
|
||||
- In a section called TITLE, write a 1 to 5 word title for the meeting.
|
||||
|
||||
- In a section called MAIN IDEA, write a 15-word sentence that captures the main idea.
|
||||
|
||||
- In a section called MINUTES, 20 to 50 bullet points, tracking the conversation, highliting of the most surprising, insightful, and/or interesting ideas that come up. If there are less than 50 then collect all of them. Make sure you extract at least 20.
|
||||
- In a section called MINUTES, write 20 to 50 bullet points, highlighting of the most surprising, insightful, and/or interesting ideas that come up in the conversation. If there are less than 50 then collect all of them. Make sure you extract at least 20.
|
||||
|
||||
- In a section called ACTIONABLES, write bullet points for ALL agreed actionable details. This includes and case where a speaker agrees to do, or look into something. If there is a deadline mentioned, include it here.
|
||||
- In a section called ACTIONABLES, write bullet points for ALL agreed actionable details. This includes cases where a speaker agrees to do or look into something. If there is a deadline mentioned, include it here.
|
||||
|
||||
- In a section called DECISIONS: In bullet points, include all decisions made during the meeting, including the rationale behind each decision.
|
||||
- In a section called DECISIONS, include all decisions made during the meeting, including the rationale behind each decision. Present them as bullet points.
|
||||
|
||||
- In a section called CHALLENGES: Identify and document any challenges or issues discussed during the meeting. Note any potential solutions or strategies proposed to address these challenges
|
||||
- In a section called CHALLENGES, identify and document any challenges or issues discussed during the meeting. Note any potential solutions or strategies proposed to address these challenges.
|
||||
|
||||
- In a section caled NEXT STEPS, Outline the next steps and action plan to be taken after the meeting
|
||||
- In a section called NEXT STEPS, outline the next steps and actions to be taken after the meeting.
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
- Only output Markdown.
|
||||
- Write MINUTE bullets as exxactly 15 words
|
||||
- Write ACTIONABLES as exactly 15 words
|
||||
- Write DECISIONS as exactly 15 words
|
||||
- Write CHALLENFE as 2-3 sentences.
|
||||
- Write NEXT STEP a 2-3 sentences
|
||||
- Write MINUTES as exactly 15 words.
|
||||
- Write ACTIONABLES as exactly 15 words.
|
||||
- Write DECISIONS as exactly 15 words.
|
||||
- Write CHALLENGES as 2-3 sentences.
|
||||
- Write NEXT STEPS as 2-3 sentences.
|
||||
- Do not give warnings or notes; only output the requested sections.
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
@@ -42,4 +42,4 @@ Take a step back and think step-by-step about how to achieve the best possible r
|
||||
|
||||
# INPUT
|
||||
|
||||
INPUT:
|
||||
INPUT:
|
||||
|
||||
@@ -5,37 +5,45 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/liushuangls/go-anthropic/v2"
|
||||
)
|
||||
|
||||
const baseUrl = "https://api.anthropic.com/v1"
|
||||
|
||||
func NewClient() (ret *Client) {
|
||||
vendorName := "Anthropic"
|
||||
ret = &Client{}
|
||||
|
||||
ret.Configurable = &common.Configurable{
|
||||
Label: vendorName,
|
||||
EnvNamePrefix: common.BuildEnvVariablePrefix(vendorName),
|
||||
ret.PluginBase = &plugins.PluginBase{
|
||||
Name: vendorName,
|
||||
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
|
||||
ConfigureCustom: ret.configure,
|
||||
}
|
||||
|
||||
ret.ApiKey = ret.Configurable.AddSetupQuestion("API key", true)
|
||||
ret.ApiBaseURL = ret.AddSetupQuestion("API Base URL", false)
|
||||
ret.ApiBaseURL.Value = baseUrl
|
||||
ret.ApiKey = ret.PluginBase.AddSetupQuestion("API key", true)
|
||||
|
||||
// we could provide a setup question for the following settings
|
||||
ret.maxTokens = 4096
|
||||
ret.defaultRequiredUserMessage = "Hi"
|
||||
ret.models = []string{
|
||||
anthropic.ModelClaude3Haiku20240307, anthropic.ModelClaude3Opus20240229,
|
||||
anthropic.ModelClaude3Opus20240229, anthropic.ModelClaude2Dot0, anthropic.ModelClaude2Dot1,
|
||||
anthropic.ModelClaudeInstant1Dot2, "claude-3-5-sonnet-20240620",
|
||||
string(anthropic.ModelClaude3Haiku20240307), string(anthropic.ModelClaude3Opus20240229),
|
||||
string(anthropic.ModelClaude3Opus20240229), string(anthropic.ModelClaude2Dot0), string(anthropic.ModelClaude2Dot1),
|
||||
string(anthropic.ModelClaudeInstant1Dot2), "claude-3-5-sonnet-20240620",
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*common.Configurable
|
||||
ApiKey *common.SetupQuestion
|
||||
*plugins.PluginBase
|
||||
ApiBaseURL *plugins.SetupQuestion
|
||||
ApiKey *plugins.SetupQuestion
|
||||
|
||||
maxTokens int
|
||||
defaultRequiredUserMessage string
|
||||
@@ -45,7 +53,11 @@ type Client struct {
|
||||
}
|
||||
|
||||
func (an *Client) configure() (err error) {
|
||||
an.client = anthropic.NewClient(an.ApiKey.Value)
|
||||
if an.ApiBaseURL.Value != "" {
|
||||
an.client = anthropic.NewClient(an.ApiKey.Value, anthropic.WithBaseURL(an.ApiBaseURL.Value))
|
||||
} else {
|
||||
an.client = anthropic.NewClient(an.ApiKey.Value)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -104,7 +116,7 @@ func (an *Client) buildMessagesRequest(msgs []*common.Message, opts *common.Chat
|
||||
messages := an.toMessages(msgs)
|
||||
|
||||
ret = anthropic.MessagesRequest{
|
||||
Model: opts.Model,
|
||||
Model: anthropic.Model(opts.Model),
|
||||
Temperature: &temperature,
|
||||
TopP: &topP,
|
||||
Messages: messages,
|
||||
@@ -121,10 +133,8 @@ func (an *Client) toMessages(msgs []*common.Message) (ret []anthropic.Message) {
|
||||
for _, msg := range normalizedMessages {
|
||||
var message anthropic.Message
|
||||
switch msg.Role {
|
||||
case "user":
|
||||
case goopenai.ChatMessageRoleUser:
|
||||
message = anthropic.NewUserTextMessage(msg.Content)
|
||||
case "system":
|
||||
message = anthropic.NewAssistantTextMessage(msg.Content)
|
||||
default:
|
||||
message = anthropic.NewAssistantTextMessage(msg.Content)
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/danielmiessler/fabric/vendors/openai"
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ func NewClient() (ret *Client) {
|
||||
|
||||
type Client struct {
|
||||
*openai.Client
|
||||
ApiDeployments *common.SetupQuestion
|
||||
ApiDeployments *plugins.SetupQuestion
|
||||
|
||||
apiDeployments []string
|
||||
}
|
||||
@@ -4,26 +4,18 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
)
|
||||
|
||||
type Client struct{}
|
||||
type Client struct {
|
||||
*plugins.PluginBase
|
||||
}
|
||||
|
||||
func NewClient() *Client {
|
||||
return &Client{}
|
||||
}
|
||||
|
||||
func (c *Client) GetName() string {
|
||||
return "DryRun"
|
||||
}
|
||||
|
||||
func (c *Client) IsConfigured() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Client) Configure() error {
|
||||
return nil
|
||||
return &Client{PluginBase: &plugins.PluginBase{Name: "DryRun"}}
|
||||
}
|
||||
|
||||
func (c *Client) ListModels() ([]string, error) {
|
||||
@@ -35,9 +27,11 @@ func (c *Client) SendStream(msgs []*common.Message, opts *common.ChatOptions, ch
|
||||
|
||||
for _, msg := range msgs {
|
||||
switch msg.Role {
|
||||
case "system":
|
||||
case goopenai.ChatMessageRoleSystem:
|
||||
output += fmt.Sprintf("System:\n%s\n\n", msg.Content)
|
||||
case "user":
|
||||
case goopenai.ChatMessageRoleAssistant:
|
||||
output += fmt.Sprintf("Assistant:\n%s\n\n", msg.Content)
|
||||
case goopenai.ChatMessageRoleUser:
|
||||
output += fmt.Sprintf("User:\n%s\n\n", msg.Content)
|
||||
default:
|
||||
output += fmt.Sprintf("%s:\n%s\n\n", msg.Role, msg.Content)
|
||||
@@ -56,14 +50,16 @@ func (c *Client) SendStream(msgs []*common.Message, opts *common.ChatOptions, ch
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) Send(ctx context.Context, msgs []*common.Message, opts *common.ChatOptions) (string, error) {
|
||||
func (c *Client) Send(_ context.Context, msgs []*common.Message, opts *common.ChatOptions) (string, error) {
|
||||
fmt.Println("Dry run: Would send the following request:")
|
||||
|
||||
for _, msg := range msgs {
|
||||
switch msg.Role {
|
||||
case "system":
|
||||
case goopenai.ChatMessageRoleSystem:
|
||||
fmt.Printf("System:\n%s\n\n", msg.Content)
|
||||
case "user":
|
||||
case goopenai.ChatMessageRoleAssistant:
|
||||
fmt.Printf("Assistant:\n%s\n\n", msg.Content)
|
||||
case goopenai.ChatMessageRoleUser:
|
||||
fmt.Printf("User:\n%s\n\n", msg.Content)
|
||||
default:
|
||||
fmt.Printf("%s:\n%s\n\n", msg.Role, msg.Content)
|
||||
@@ -84,6 +80,6 @@ func (c *Client) Setup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) SetupFillEnvFileContent(buffer *bytes.Buffer) {
|
||||
func (c *Client) SetupFillEnvFileContent(_ *bytes.Buffer) {
|
||||
// No environment variables needed for dry run
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
@@ -18,19 +19,19 @@ func NewClient() (ret *Client) {
|
||||
vendorName := "Gemini"
|
||||
ret = &Client{}
|
||||
|
||||
ret.Configurable = &common.Configurable{
|
||||
Label: vendorName,
|
||||
EnvNamePrefix: common.BuildEnvVariablePrefix(vendorName),
|
||||
ret.PluginBase = &plugins.PluginBase{
|
||||
Name: vendorName,
|
||||
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
|
||||
}
|
||||
|
||||
ret.ApiKey = ret.Configurable.AddSetupQuestion("API key", true)
|
||||
ret.ApiKey = ret.PluginBase.AddSetupQuestion("API key", true)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*common.Configurable
|
||||
ApiKey *common.SetupQuestion
|
||||
*plugins.PluginBase
|
||||
ApiKey *plugins.SetupQuestion
|
||||
}
|
||||
|
||||
func (o *Client) ListModels() (ret []string, err error) {
|
||||
@@ -1,7 +1,7 @@
|
||||
package groq
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/vendors/openai"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
)
|
||||
|
||||
func NewClient() (ret *Client) {
|
||||
15
plugins/ai/mistral/mistral.go
Normal file
15
plugins/ai/mistral/mistral.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package mistral
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
)
|
||||
|
||||
func NewClient() (ret *Client) {
|
||||
ret = &Client{}
|
||||
ret.Client = openai.NewClientCompatible("Mistral", "https://api.mistral.ai/v1", nil)
|
||||
return
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*openai.Client
|
||||
}
|
||||
13
plugins/ai/models.go
Normal file
13
plugins/ai/models.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
)
|
||||
|
||||
func NewVendorsModels() *VendorsModels {
|
||||
return &VendorsModels{GroupsItemsSelectorString: common.NewGroupsItemsSelectorString("Available models")}
|
||||
}
|
||||
|
||||
type VendorsModels struct {
|
||||
*common.GroupsItemsSelectorString
|
||||
}
|
||||
33
plugins/ai/models_test.go
Normal file
33
plugins/ai/models_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewVendorsModels(t *testing.T) {
|
||||
vendors := NewVendorsModels()
|
||||
if vendors == nil {
|
||||
t.Fatalf("NewVendorsModels() returned nil")
|
||||
}
|
||||
if len(vendors.GroupsItems) != 0 {
|
||||
t.Fatalf("NewVendorsModels() returned non-empty VendorsModels map")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindVendorsByModelFirst(t *testing.T) {
|
||||
vendors := NewVendorsModels()
|
||||
vendors.AddGroupItems("vendor1", []string{"model1", "model2"}...)
|
||||
vendor := vendors.FindGroupsByItemFirst("model1")
|
||||
if vendor != "vendor1" {
|
||||
t.Fatalf("FindVendorsByModelFirst() = %v, want %v", vendor, "vendor1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindVendorsByModel(t *testing.T) {
|
||||
vendors := NewVendorsModels()
|
||||
vendors.AddGroupItems("vendor1", []string{"model1", "model2"}...)
|
||||
foundVendors := vendors.FindGroupsByItem("model1")
|
||||
if len(foundVendors) != 1 || foundVendors[0] != "vendor1" {
|
||||
t.Fatalf("FindVendorsByModel() = %v, want %v", foundVendors, []string{"vendor1"})
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package ollama
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
@@ -17,21 +18,21 @@ func NewClient() (ret *Client) {
|
||||
vendorName := "Ollama"
|
||||
ret = &Client{}
|
||||
|
||||
ret.Configurable = &common.Configurable{
|
||||
Label: vendorName,
|
||||
EnvNamePrefix: common.BuildEnvVariablePrefix(vendorName),
|
||||
ret.PluginBase = &plugins.PluginBase{
|
||||
Name: vendorName,
|
||||
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
|
||||
ConfigureCustom: ret.configure,
|
||||
}
|
||||
|
||||
ret.ApiUrl = ret.Configurable.AddSetupQuestionCustom("API URL", true,
|
||||
ret.ApiUrl = ret.AddSetupQuestionCustom("API URL", true,
|
||||
"Enter your Ollama URL (as a reminder, it is usually http://localhost:11434)")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*common.Configurable
|
||||
ApiUrl *common.SetupQuestion
|
||||
*plugins.PluginBase
|
||||
ApiUrl *plugins.SetupQuestion
|
||||
|
||||
apiUrl *url.URL
|
||||
client *ollamaapi.Client
|
||||
@@ -4,7 +4,9 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"io"
|
||||
"log/slog"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/samber/lo"
|
||||
@@ -23,9 +25,9 @@ func NewClientCompatible(vendorName string, defaultBaseUrl string, configureCust
|
||||
configureCustom = ret.configure
|
||||
}
|
||||
|
||||
ret.Configurable = &common.Configurable{
|
||||
Label: vendorName,
|
||||
EnvNamePrefix: common.BuildEnvVariablePrefix(vendorName),
|
||||
ret.PluginBase = &plugins.PluginBase{
|
||||
Name: vendorName,
|
||||
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
|
||||
ConfigureCustom: configureCustom,
|
||||
}
|
||||
|
||||
@@ -37,9 +39,9 @@ func NewClientCompatible(vendorName string, defaultBaseUrl string, configureCust
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*common.Configurable
|
||||
ApiKey *common.SetupQuestion
|
||||
ApiBaseURL *common.SetupQuestion
|
||||
*plugins.PluginBase
|
||||
ApiKey *plugins.SetupQuestion
|
||||
ApiBaseURL *plugins.SetupQuestion
|
||||
ApiClient *openai.Client
|
||||
}
|
||||
|
||||
@@ -82,7 +84,13 @@ func (o *Client) SendStream(
|
||||
for {
|
||||
var response openai.ChatCompletionStreamResponse
|
||||
if response, err = stream.Recv(); err == nil {
|
||||
channel <- response.Choices[0].Delta.Content
|
||||
if len(response.Choices) > 0 {
|
||||
channel <- response.Choices[0].Delta.Content
|
||||
} else {
|
||||
channel <- "\n"
|
||||
close(channel)
|
||||
break
|
||||
}
|
||||
} else if errors.Is(err, io.EOF) {
|
||||
channel <- "\n"
|
||||
close(channel)
|
||||
@@ -103,7 +111,10 @@ func (o *Client) Send(ctx context.Context, msgs []*common.Message, opts *common.
|
||||
if resp, err = o.ApiClient.CreateChatCompletion(ctx, req); err != nil {
|
||||
return
|
||||
}
|
||||
ret = resp.Choices[0].Message.Content
|
||||
if len(resp.Choices) > 0 {
|
||||
ret = resp.Choices[0].Message.Content
|
||||
slog.Debug("SystemFingerprint: " + resp.SystemFingerprint)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -111,26 +122,35 @@ func (o *Client) buildChatCompletionRequest(
|
||||
msgs []*common.Message, opts *common.ChatOptions,
|
||||
) (ret goopenai.ChatCompletionRequest) {
|
||||
messages := lo.Map(msgs, func(message *common.Message, _ int) goopenai.ChatCompletionMessage {
|
||||
var role string
|
||||
|
||||
switch message.Role {
|
||||
case "user":
|
||||
role = goopenai.ChatMessageRoleUser
|
||||
case "system":
|
||||
role = goopenai.ChatMessageRoleSystem
|
||||
default:
|
||||
role = goopenai.ChatMessageRoleSystem
|
||||
}
|
||||
return goopenai.ChatCompletionMessage{Role: role, Content: message.Content}
|
||||
return goopenai.ChatCompletionMessage{Role: message.Role, Content: message.Content}
|
||||
})
|
||||
|
||||
ret = goopenai.ChatCompletionRequest{
|
||||
Model: opts.Model,
|
||||
Temperature: float32(opts.Temperature),
|
||||
TopP: float32(opts.TopP),
|
||||
PresencePenalty: float32(opts.PresencePenalty),
|
||||
FrequencyPenalty: float32(opts.FrequencyPenalty),
|
||||
Messages: messages,
|
||||
if opts.Raw {
|
||||
ret = goopenai.ChatCompletionRequest{
|
||||
Model: opts.Model,
|
||||
Messages: messages,
|
||||
}
|
||||
} else {
|
||||
if opts.Seed == 0 {
|
||||
ret = goopenai.ChatCompletionRequest{
|
||||
Model: opts.Model,
|
||||
Temperature: float32(opts.Temperature),
|
||||
TopP: float32(opts.TopP),
|
||||
PresencePenalty: float32(opts.PresencePenalty),
|
||||
FrequencyPenalty: float32(opts.FrequencyPenalty),
|
||||
Messages: messages,
|
||||
}
|
||||
} else {
|
||||
ret = goopenai.ChatCompletionRequest{
|
||||
Model: opts.Model,
|
||||
Temperature: float32(opts.Temperature),
|
||||
TopP: float32(opts.TopP),
|
||||
PresencePenalty: float32(opts.PresencePenalty),
|
||||
FrequencyPenalty: float32(opts.FrequencyPenalty),
|
||||
Messages: messages,
|
||||
Seed: &opts.Seed,
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
102
plugins/ai/openai/openai_test.go
Normal file
102
plugins/ai/openai/openai_test.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package openai
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBuildChatCompletionRequestPinSeed(t *testing.T) {
|
||||
|
||||
var msgs []*common.Message
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
msgs = append(msgs, &common.Message{
|
||||
Role: "User",
|
||||
Content: "My msg",
|
||||
})
|
||||
}
|
||||
|
||||
opts := &common.ChatOptions{
|
||||
Temperature: 0.8,
|
||||
TopP: 0.9,
|
||||
PresencePenalty: 0.1,
|
||||
FrequencyPenalty: 0.2,
|
||||
Raw: false,
|
||||
Seed: 1,
|
||||
}
|
||||
|
||||
var expectedMessages []openai.ChatCompletionMessage
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
expectedMessages = append(expectedMessages,
|
||||
openai.ChatCompletionMessage{
|
||||
Role: msgs[i].Role,
|
||||
Content: msgs[i].Content,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
var expectedRequest = goopenai.ChatCompletionRequest{
|
||||
Model: opts.Model,
|
||||
Temperature: float32(opts.Temperature),
|
||||
TopP: float32(opts.TopP),
|
||||
PresencePenalty: float32(opts.PresencePenalty),
|
||||
FrequencyPenalty: float32(opts.FrequencyPenalty),
|
||||
Messages: expectedMessages,
|
||||
Seed: &opts.Seed,
|
||||
}
|
||||
|
||||
var client = NewClient()
|
||||
request := client.buildChatCompletionRequest(msgs, opts)
|
||||
assert.Equal(t, expectedRequest, request)
|
||||
}
|
||||
|
||||
func TestBuildChatCompletionRequestNilSeed(t *testing.T) {
|
||||
|
||||
var msgs []*common.Message
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
msgs = append(msgs, &common.Message{
|
||||
Role: "User",
|
||||
Content: "My msg",
|
||||
})
|
||||
}
|
||||
|
||||
opts := &common.ChatOptions{
|
||||
Temperature: 0.8,
|
||||
TopP: 0.9,
|
||||
PresencePenalty: 0.1,
|
||||
FrequencyPenalty: 0.2,
|
||||
Raw: false,
|
||||
Seed: 0,
|
||||
}
|
||||
|
||||
var expectedMessages []openai.ChatCompletionMessage
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
expectedMessages = append(expectedMessages,
|
||||
openai.ChatCompletionMessage{
|
||||
Role: msgs[i].Role,
|
||||
Content: msgs[i].Content,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
var expectedRequest = goopenai.ChatCompletionRequest{
|
||||
Model: opts.Model,
|
||||
Temperature: float32(opts.Temperature),
|
||||
TopP: float32(opts.TopP),
|
||||
PresencePenalty: float32(opts.PresencePenalty),
|
||||
FrequencyPenalty: float32(opts.FrequencyPenalty),
|
||||
Messages: expectedMessages,
|
||||
Seed: nil,
|
||||
}
|
||||
|
||||
var client = NewClient()
|
||||
request := client.buildChatCompletionRequest(msgs, opts)
|
||||
assert.Equal(t, expectedRequest, request)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package openrouter
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/vendors/openai"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
)
|
||||
|
||||
func NewClient() (ret *Client) {
|
||||
@@ -1,7 +1,7 @@
|
||||
package siliconcloud
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/vendors/openai"
|
||||
"github.com/danielmiessler/fabric/plugins/ai/openai"
|
||||
)
|
||||
|
||||
func NewClient() (ret *Client) {
|
||||
@@ -1,19 +1,15 @@
|
||||
package vendors
|
||||
package ai
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
)
|
||||
|
||||
type Vendor interface {
|
||||
GetName() string
|
||||
IsConfigured() bool
|
||||
Configure() error
|
||||
plugins.Plugin
|
||||
ListModels() ([]string, error)
|
||||
SendStream([]*common.Message, *common.ChatOptions, chan string) error
|
||||
Send(context.Context, []*common.Message, *common.ChatOptions) (string, error)
|
||||
Setup() error
|
||||
SetupFillEnvFileContent(*bytes.Buffer)
|
||||
}
|
||||
147
plugins/ai/vendors.go
Normal file
147
plugins/ai/vendors.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func NewVendorsManager() *VendorsManager {
|
||||
return &VendorsManager{
|
||||
Vendors: []Vendor{},
|
||||
VendorsByName: map[string]Vendor{},
|
||||
}
|
||||
}
|
||||
|
||||
type VendorsManager struct {
|
||||
*plugins.PluginBase
|
||||
Vendors []Vendor
|
||||
VendorsByName map[string]Vendor
|
||||
Models *VendorsModels
|
||||
}
|
||||
|
||||
func (o *VendorsManager) AddVendors(vendors ...Vendor) {
|
||||
for _, vendor := range vendors {
|
||||
o.VendorsByName[vendor.GetName()] = vendor
|
||||
o.Vendors = append(o.Vendors, vendor)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *VendorsManager) SetupFillEnvFileContent(envFileContent *bytes.Buffer) {
|
||||
for _, vendor := range o.Vendors {
|
||||
vendor.SetupFillEnvFileContent(envFileContent)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *VendorsManager) GetModels() (ret *VendorsModels, err error) {
|
||||
if o.Models == nil {
|
||||
err = o.readModels()
|
||||
}
|
||||
ret = o.Models
|
||||
return
|
||||
}
|
||||
|
||||
func (o *VendorsManager) Configure() (err error) {
|
||||
for _, vendor := range o.Vendors {
|
||||
_ = vendor.Configure()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *VendorsManager) HasVendors() bool {
|
||||
return len(o.Vendors) > 0
|
||||
}
|
||||
|
||||
func (o *VendorsManager) FindByName(name string) Vendor {
|
||||
return o.VendorsByName[name]
|
||||
}
|
||||
|
||||
func (o *VendorsManager) readModels() (err error) {
|
||||
if len(o.Vendors) == 0 {
|
||||
|
||||
err = fmt.Errorf("no AI vendors configured to read models from. Please configure at least one AI vendor")
|
||||
return
|
||||
}
|
||||
|
||||
o.Models = NewVendorsModels()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
resultsChan := make(chan modelResult, len(o.Vendors))
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
for _, vendor := range o.Vendors {
|
||||
wg.Add(1)
|
||||
go o.fetchVendorModels(ctx, &wg, vendor, resultsChan)
|
||||
}
|
||||
|
||||
// Wait for all goroutines to finish
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(resultsChan)
|
||||
}()
|
||||
|
||||
// Collect results
|
||||
for result := range resultsChan {
|
||||
if result.err != nil {
|
||||
fmt.Println(result.vendorName, result.err)
|
||||
cancel() // Cancel remaining goroutines if needed
|
||||
} else {
|
||||
o.Models.AddGroupItems(result.vendorName, result.models...)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *VendorsManager) fetchVendorModels(
|
||||
ctx context.Context, wg *sync.WaitGroup, vendor Vendor, resultsChan chan<- modelResult) {
|
||||
|
||||
defer wg.Done()
|
||||
|
||||
models, err := vendor.ListModels()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Context canceled, don't send the result
|
||||
return
|
||||
case resultsChan <- modelResult{vendorName: vendor.GetName(), models: models, err: err}:
|
||||
// Result sent
|
||||
}
|
||||
}
|
||||
|
||||
func (o *VendorsManager) Setup() (ret map[string]Vendor, err error) {
|
||||
ret = map[string]Vendor{}
|
||||
for _, vendor := range o.Vendors {
|
||||
fmt.Println()
|
||||
o.setupVendorTo(vendor, ret)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *VendorsManager) SetupVendor(vendorName string, configuredVendors map[string]Vendor) (err error) {
|
||||
vendor := o.FindByName(vendorName)
|
||||
if vendor == nil {
|
||||
err = fmt.Errorf("vendor %s not found", vendorName)
|
||||
return
|
||||
}
|
||||
o.setupVendorTo(vendor, configuredVendors)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *VendorsManager) setupVendorTo(vendor Vendor, configuredVendors map[string]Vendor) {
|
||||
if vendorErr := vendor.Setup(); vendorErr == nil {
|
||||
fmt.Printf("[%v] configured\n", vendor.GetName())
|
||||
configuredVendors[vendor.GetName()] = vendor
|
||||
} else {
|
||||
delete(configuredVendors, vendor.GetName())
|
||||
fmt.Printf("[%v] skipped\n", vendor.GetName())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type modelResult struct {
|
||||
vendorName string
|
||||
models []string
|
||||
err error
|
||||
}
|
||||
13
plugins/db/api.go
Normal file
13
plugins/db/api.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package db
|
||||
|
||||
type Storage[T any] interface {
|
||||
Configure() (err error)
|
||||
Get(name string) (ret *T, err error)
|
||||
GetNames() (ret []string, err error)
|
||||
Delete(name string) (err error)
|
||||
Exists(name string) (ret bool)
|
||||
Rename(oldName, newName string) (err error)
|
||||
Save(name string, content []byte) (err error)
|
||||
Load(name string) (ret []byte, err error)
|
||||
ListNames() (err error)
|
||||
}
|
||||
32
plugins/db/fsdb/contexts.go
Normal file
32
plugins/db/fsdb/contexts.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package fsdb
|
||||
|
||||
import "fmt"
|
||||
|
||||
type ContextsEntity struct {
|
||||
*StorageEntity
|
||||
}
|
||||
|
||||
// Get Load a context from file
|
||||
func (o *ContextsEntity) Get(name string) (ret *Context, err error) {
|
||||
var content []byte
|
||||
if content, err = o.Load(name); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ret = &Context{Name: name, Content: string(content)}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *ContextsEntity) PrintContext(name string) (err error) {
|
||||
var context *Context
|
||||
if context, err = o.Get(name); err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(context.Content)
|
||||
return
|
||||
}
|
||||
|
||||
type Context struct {
|
||||
Name string
|
||||
Content string
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package db
|
||||
package fsdb
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
|
||||
func TestContexts_GetContext(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
contexts := &Contexts{
|
||||
Storage: &Storage{Dir: dir},
|
||||
contexts := &ContextsEntity{
|
||||
StorageEntity: &StorageEntity{Dir: dir},
|
||||
}
|
||||
contextName := "testContext"
|
||||
contextPath := filepath.Join(dir, contextName)
|
||||
@@ -18,7 +18,7 @@ func TestContexts_GetContext(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write context file: %v", err)
|
||||
}
|
||||
context, err := contexts.GetContext(contextName)
|
||||
context, err := contexts.Get(contextName)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get context: %v", err)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package db
|
||||
package fsdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -14,17 +14,17 @@ func NewDb(dir string) (db *Db) {
|
||||
|
||||
db.EnvFilePath = db.FilePath(".env")
|
||||
|
||||
db.Patterns = &Patterns{
|
||||
Storage: &Storage{Label: "Patterns", Dir: db.FilePath("patterns"), ItemIsDir: true},
|
||||
db.Patterns = &PatternsEntity{
|
||||
StorageEntity: &StorageEntity{Label: "Patterns", Dir: db.FilePath("patterns"), ItemIsDir: true},
|
||||
SystemPatternFile: "system.md",
|
||||
UniquePatternsFilePath: db.FilePath("unique_patterns.txt"),
|
||||
}
|
||||
|
||||
db.Sessions = &Sessions{
|
||||
&Storage{Label: "Sessions", Dir: db.FilePath("sessions"), FileExtension: ".json"}}
|
||||
db.Sessions = &SessionsEntity{
|
||||
&StorageEntity{Label: "Sessions", Dir: db.FilePath("sessions"), FileExtension: ".json"}}
|
||||
|
||||
db.Contexts = &Contexts{
|
||||
&Storage{Label: "Contexts", Dir: db.FilePath("contexts")}}
|
||||
db.Contexts = &ContextsEntity{
|
||||
&StorageEntity{Label: "Contexts", Dir: db.FilePath("contexts")}}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -32,9 +32,9 @@ func NewDb(dir string) (db *Db) {
|
||||
type Db struct {
|
||||
Dir string
|
||||
|
||||
Patterns *Patterns
|
||||
Sessions *Sessions
|
||||
Contexts *Contexts
|
||||
Patterns *PatternsEntity
|
||||
Sessions *SessionsEntity
|
||||
Contexts *ContextsEntity
|
||||
|
||||
EnvFilePath string
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package db
|
||||
package fsdb
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -1,4 +1,4 @@
|
||||
package db
|
||||
package fsdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -7,14 +7,13 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Patterns struct {
|
||||
*Storage
|
||||
type PatternsEntity struct {
|
||||
*StorageEntity
|
||||
SystemPatternFile string
|
||||
UniquePatternsFilePath string
|
||||
}
|
||||
|
||||
// GetPattern finds a pattern by name and returns the pattern as an entry or an error
|
||||
func (o *Patterns) GetPattern(name string, variables map[string]string) (ret *Pattern, err error) {
|
||||
func (o *PatternsEntity) Get(name string) (ret *Pattern, err error) {
|
||||
patternPath := filepath.Join(o.Dir, name, o.SystemPatternFile)
|
||||
|
||||
var pattern []byte
|
||||
@@ -23,13 +22,6 @@ func (o *Patterns) GetPattern(name string, variables map[string]string) (ret *Pa
|
||||
}
|
||||
|
||||
patternStr := string(pattern)
|
||||
|
||||
if variables != nil && len(variables) > 0 {
|
||||
for variableName, value := range variables {
|
||||
patternStr = strings.ReplaceAll(patternStr, variableName, value)
|
||||
}
|
||||
}
|
||||
|
||||
ret = &Pattern{
|
||||
Name: name,
|
||||
Pattern: patternStr,
|
||||
@@ -37,7 +29,22 @@ func (o *Patterns) GetPattern(name string, variables map[string]string) (ret *Pa
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Patterns) PrintLatestPatterns(latestNumber int) (err error) {
|
||||
// GetApplyVariables finds a pattern by name and returns the pattern as an entry or an error
|
||||
func (o *PatternsEntity) GetApplyVariables(name string, variables map[string]string) (ret *Pattern, err error) {
|
||||
|
||||
if ret, err = o.Get(name); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if variables != nil && len(variables) > 0 {
|
||||
for variableName, value := range variables {
|
||||
ret.Pattern = strings.ReplaceAll(ret.Pattern, variableName, value)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *PatternsEntity) PrintLatestPatterns(latestNumber int) (err error) {
|
||||
var contents []byte
|
||||
if contents, err = os.ReadFile(o.UniquePatternsFilePath); err != nil {
|
||||
err = fmt.Errorf("could not read unique patterns file. Pleas run --updatepatterns (%s)", err)
|
||||
1
plugins/db/fsdb/patterns_test.go
Normal file
1
plugins/db/fsdb/patterns_test.go
Normal file
@@ -0,0 +1 @@
|
||||
package fsdb
|
||||
88
plugins/db/fsdb/sessions.go
Normal file
88
plugins/db/fsdb/sessions.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package fsdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
)
|
||||
|
||||
type SessionsEntity struct {
|
||||
*StorageEntity
|
||||
}
|
||||
|
||||
func (o *SessionsEntity) Get(name string) (session *Session, err error) {
|
||||
session = &Session{Name: name}
|
||||
|
||||
if o.Exists(name) {
|
||||
err = o.LoadAsJson(name, &session.Messages)
|
||||
} else {
|
||||
fmt.Printf("Creating new session: %s\n", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *SessionsEntity) PrintSession(name string) (err error) {
|
||||
if o.Exists(name) {
|
||||
var session Session
|
||||
if err = o.LoadAsJson(name, &session.Messages); err == nil {
|
||||
fmt.Println(session.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *SessionsEntity) SaveSession(session *Session) (err error) {
|
||||
return o.SaveAsJson(session.Name, session.Messages)
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
Name string
|
||||
Messages []*common.Message
|
||||
|
||||
vendorMessages []*common.Message
|
||||
}
|
||||
|
||||
func (o *Session) IsEmpty() bool {
|
||||
return len(o.Messages) == 0
|
||||
}
|
||||
|
||||
func (o *Session) Append(messages ...*common.Message) {
|
||||
if o.vendorMessages != nil {
|
||||
for _, message := range messages {
|
||||
o.Messages = append(o.Messages, message)
|
||||
o.appendVendorMessage(message)
|
||||
}
|
||||
} else {
|
||||
o.Messages = append(o.Messages, messages...)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Session) GetVendorMessages() (ret []*common.Message) {
|
||||
if o.vendorMessages == nil {
|
||||
o.vendorMessages = []*common.Message{}
|
||||
for _, message := range o.Messages {
|
||||
o.appendVendorMessage(message)
|
||||
}
|
||||
}
|
||||
ret = o.vendorMessages
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Session) appendVendorMessage(message *common.Message) {
|
||||
if message.Role != common.ChatMessageRoleMeta {
|
||||
o.vendorMessages = append(o.vendorMessages, message)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Session) GetLastMessage() (ret *common.Message) {
|
||||
if len(o.Messages) > 0 {
|
||||
ret = o.Messages[len(o.Messages)-1]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Session) String() (ret string) {
|
||||
for _, message := range o.Messages {
|
||||
ret += fmt.Sprintf("\n--- \n[%v]\n\n%v", message.Role, message.Content)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package db
|
||||
package fsdb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
|
||||
func TestSessions_GetOrCreateSession(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
sessions := &Sessions{
|
||||
Storage: &Storage{Dir: dir, FileExtension: ".json"},
|
||||
sessions := &SessionsEntity{
|
||||
StorageEntity: &StorageEntity{Dir: dir, FileExtension: ".json"},
|
||||
}
|
||||
sessionName := "testSession"
|
||||
session, err := sessions.GetOrCreateSession(sessionName)
|
||||
session, err := sessions.Get(sessionName)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get or create session: %v", err)
|
||||
}
|
||||
@@ -23,8 +23,8 @@ func TestSessions_GetOrCreateSession(t *testing.T) {
|
||||
|
||||
func TestSessions_SaveSession(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
sessions := &Sessions{
|
||||
Storage: &Storage{Dir: dir, FileExtension: ".json"},
|
||||
sessions := &SessionsEntity{
|
||||
StorageEntity: &StorageEntity{Dir: dir, FileExtension: ".json"},
|
||||
}
|
||||
sessionName := "testSession"
|
||||
session := &Session{Name: sessionName, Messages: []*common.Message{{Content: "message1"}}}
|
||||
@@ -1,22 +1,23 @@
|
||||
package db
|
||||
package fsdb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/samber/lo"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
type StorageEntity struct {
|
||||
Label string
|
||||
Dir string
|
||||
ItemIsDir bool
|
||||
FileExtension string
|
||||
}
|
||||
|
||||
func (o *Storage) Configure() (err error) {
|
||||
func (o *StorageEntity) Configure() (err error) {
|
||||
if err = os.MkdirAll(o.Dir, os.ModePerm); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -24,7 +25,7 @@ func (o *Storage) Configure() (err error) {
|
||||
}
|
||||
|
||||
// GetNames finds all patterns in the patterns directory and enters the id, name, and pattern into a slice of Entry structs. it returns these entries or an error
|
||||
func (o *Storage) GetNames() (ret []string, err error) {
|
||||
func (o *StorageEntity) GetNames() (ret []string, err error) {
|
||||
var entries []os.DirEntry
|
||||
if entries, err = os.ReadDir(o.Dir); err != nil {
|
||||
err = fmt.Errorf("could not read items from directory: %v", err)
|
||||
@@ -58,7 +59,41 @@ func (o *Storage) GetNames() (ret []string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Storage) ListNames() (err error) {
|
||||
func (o *StorageEntity) Delete(name string) (err error) {
|
||||
if err = os.Remove(o.BuildFilePathByName(name)); err != nil {
|
||||
err = fmt.Errorf("could not delete %s: %v", name, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *StorageEntity) Exists(name string) (ret bool) {
|
||||
_, err := os.Stat(o.BuildFilePathByName(name))
|
||||
ret = !os.IsNotExist(err)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *StorageEntity) Rename(oldName, newName string) (err error) {
|
||||
if err = os.Rename(o.BuildFilePathByName(oldName), o.BuildFilePathByName(newName)); err != nil {
|
||||
err = fmt.Errorf("could not rename %s to %s: %v", oldName, newName, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *StorageEntity) Save(name string, content []byte) (err error) {
|
||||
if err = os.WriteFile(o.BuildFilePathByName(name), content, 0644); err != nil {
|
||||
err = fmt.Errorf("could not save %s: %v", name, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *StorageEntity) Load(name string) (ret []byte, err error) {
|
||||
if ret, err = os.ReadFile(o.BuildFilePathByName(name)); err != nil {
|
||||
err = fmt.Errorf("could not load %s: %v", name, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *StorageEntity) ListNames() (err error) {
|
||||
var names []string
|
||||
if names, err = o.GetNames(); err != nil {
|
||||
return
|
||||
@@ -69,62 +104,27 @@ func (o *Storage) ListNames() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("\n%v:\n", o.Label)
|
||||
for _, item := range names {
|
||||
fmt.Printf("\t%s\n", item)
|
||||
fmt.Printf("%s\n", item)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Storage) BuildFilePathByName(name string) (ret string) {
|
||||
func (o *StorageEntity) BuildFilePathByName(name string) (ret string) {
|
||||
ret = o.BuildFilePath(o.buildFileName(name))
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Storage) BuildFilePath(fileName string) (ret string) {
|
||||
func (o *StorageEntity) BuildFilePath(fileName string) (ret string) {
|
||||
ret = filepath.Join(o.Dir, fileName)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Storage) buildFileName(name string) string {
|
||||
func (o *StorageEntity) buildFileName(name string) string {
|
||||
return fmt.Sprintf("%s%v", name, o.FileExtension)
|
||||
}
|
||||
|
||||
func (o *Storage) Delete(name string) (err error) {
|
||||
if err = os.Remove(o.BuildFilePathByName(name)); err != nil {
|
||||
err = fmt.Errorf("could not delete %s: %v", name, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Storage) Exists(name string) (ret bool) {
|
||||
_, err := os.Stat(o.BuildFilePathByName(name))
|
||||
ret = !os.IsNotExist(err)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Storage) Rename(oldName, newName string) (err error) {
|
||||
if err = os.Rename(o.BuildFilePathByName(oldName), o.BuildFilePathByName(newName)); err != nil {
|
||||
err = fmt.Errorf("could not rename %s to %s: %v", oldName, newName, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Storage) Save(name string, content []byte) (err error) {
|
||||
if err = os.WriteFile(o.BuildFilePathByName(name), content, 0644); err != nil {
|
||||
err = fmt.Errorf("could not save %s: %v", name, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Storage) Load(name string) (ret []byte, err error) {
|
||||
if ret, err = os.ReadFile(o.BuildFilePathByName(name)); err != nil {
|
||||
err = fmt.Errorf("could not load %s: %v", name, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Storage) SaveAsJson(name string, item interface{}) (err error) {
|
||||
func (o *StorageEntity) SaveAsJson(name string, item interface{}) (err error) {
|
||||
var jsonString []byte
|
||||
if jsonString, err = json.Marshal(item); err == nil {
|
||||
err = o.Save(name, jsonString)
|
||||
@@ -135,7 +135,7 @@ func (o *Storage) SaveAsJson(name string, item interface{}) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *Storage) LoadAsJson(name string, item interface{}) (err error) {
|
||||
func (o *StorageEntity) LoadAsJson(name string, item interface{}) (err error) {
|
||||
var content []byte
|
||||
if content, err = o.Load(name); err != nil {
|
||||
return
|
||||
@@ -1,4 +1,4 @@
|
||||
package db
|
||||
package fsdb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
func TestStorage_SaveAndLoad(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
storage := &Storage{Dir: dir}
|
||||
storage := &StorageEntity{Dir: dir}
|
||||
name := "test"
|
||||
content := []byte("test content")
|
||||
if err := storage.Save(name, content); err != nil {
|
||||
@@ -23,7 +23,7 @@ func TestStorage_SaveAndLoad(t *testing.T) {
|
||||
|
||||
func TestStorage_Exists(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
storage := &Storage{Dir: dir}
|
||||
storage := &StorageEntity{Dir: dir}
|
||||
name := "test"
|
||||
if storage.Exists(name) {
|
||||
t.Errorf("expected file to not exist")
|
||||
@@ -38,7 +38,7 @@ func TestStorage_Exists(t *testing.T) {
|
||||
|
||||
func TestStorage_Delete(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
storage := &Storage{Dir: dir}
|
||||
storage := &StorageEntity{Dir: dir}
|
||||
name := "test"
|
||||
if err := storage.Save(name, []byte("test content")); err != nil {
|
||||
t.Fatalf("failed to save content: %v", err)
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -9,41 +9,58 @@ import (
|
||||
|
||||
const AnswerReset = "reset"
|
||||
|
||||
type Configurable struct {
|
||||
type Plugin interface {
|
||||
GetName() string
|
||||
GetSetupDescription() string
|
||||
IsConfigured() bool
|
||||
Configure() error
|
||||
Setup() error
|
||||
SetupFillEnvFileContent(*bytes.Buffer)
|
||||
}
|
||||
|
||||
type PluginBase struct {
|
||||
Settings
|
||||
SetupQuestions
|
||||
|
||||
Label string
|
||||
EnvNamePrefix string
|
||||
Name string
|
||||
SetupDescription string
|
||||
EnvNamePrefix string
|
||||
|
||||
ConfigureCustom func() error
|
||||
}
|
||||
|
||||
func (o *Configurable) GetName() string {
|
||||
return o.Label
|
||||
func (o *PluginBase) GetName() string {
|
||||
return o.Name
|
||||
}
|
||||
|
||||
func (o *Configurable) AddSetting(name string, required bool) (ret *Setting) {
|
||||
func (o *PluginBase) GetSetupDescription() (ret string) {
|
||||
if ret = o.SetupDescription; ret == "" {
|
||||
ret = o.GetName()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *PluginBase) AddSetting(name string, required bool) (ret *Setting) {
|
||||
ret = NewSetting(fmt.Sprintf("%v%v", o.EnvNamePrefix, BuildEnvVariable(name)), required)
|
||||
o.Settings = append(o.Settings, ret)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Configurable) AddSetupQuestion(name string, required bool) (ret *SetupQuestion) {
|
||||
func (o *PluginBase) AddSetupQuestion(name string, required bool) (ret *SetupQuestion) {
|
||||
return o.AddSetupQuestionCustom(name, required, "")
|
||||
}
|
||||
|
||||
func (o *Configurable) AddSetupQuestionCustom(name string, required bool, question string) (ret *SetupQuestion) {
|
||||
func (o *PluginBase) AddSetupQuestionCustom(name string, required bool, question string) (ret *SetupQuestion) {
|
||||
setting := o.AddSetting(name, required)
|
||||
ret = &SetupQuestion{Setting: setting, Question: question}
|
||||
if ret.Question == "" {
|
||||
ret.Question = fmt.Sprintf("Enter your %v %v", o.Label, strings.ToUpper(name))
|
||||
ret.Question = fmt.Sprintf("Enter your %v %v", o.Name, strings.ToUpper(name))
|
||||
}
|
||||
o.SetupQuestions = append(o.SetupQuestions, ret)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Configurable) Configure() (err error) {
|
||||
func (o *PluginBase) Configure() (err error) {
|
||||
if err = o.Settings.Configure(); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -54,8 +71,8 @@ func (o *Configurable) Configure() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Configurable) Setup() (err error) {
|
||||
if err = o.Ask(o.Label); err != nil {
|
||||
func (o *PluginBase) Setup() (err error) {
|
||||
if err = o.Ask(o.Name); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -63,14 +80,14 @@ func (o *Configurable) Setup() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Configurable) SetupOrSkip() (err error) {
|
||||
func (o *PluginBase) SetupOrSkip() (err error) {
|
||||
if err = o.Setup(); err != nil {
|
||||
fmt.Printf("[%v] skipped\n", o.GetName())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Configurable) SetupFillEnvFileContent(fileEnvFileContent *bytes.Buffer) {
|
||||
func (o *PluginBase) SetupFillEnvFileContent(fileEnvFileContent *bytes.Buffer) {
|
||||
o.Settings.FillEnvFileContent(fileEnvFileContent)
|
||||
}
|
||||
|
||||
@@ -103,8 +120,9 @@ func (o *Setting) IsDefined() bool {
|
||||
}
|
||||
|
||||
func (o *Setting) Configure() error {
|
||||
if o.Value == "" {
|
||||
o.Value = os.Getenv(o.EnvVariable)
|
||||
envValue := os.Getenv(o.EnvVariable)
|
||||
if envValue != "" {
|
||||
o.Value = envValue
|
||||
}
|
||||
return o.IsValidErr()
|
||||
}
|
||||
@@ -125,6 +143,10 @@ func (o *Setting) Print() {
|
||||
fmt.Printf("%v: %v\n", o.EnvVariable, o.Value)
|
||||
}
|
||||
|
||||
func NewSetupQuestion(question string) *SetupQuestion {
|
||||
return &SetupQuestion{Setting: &Setting{}, Question: question}
|
||||
}
|
||||
|
||||
type SetupQuestion struct {
|
||||
*Setting
|
||||
Question string
|
||||
@@ -161,6 +183,11 @@ func (o *SetupQuestion) Ask(label string) (err error) {
|
||||
|
||||
func (o *SetupQuestion) OnAnswer(answer string) (err error) {
|
||||
o.Value = answer
|
||||
if o.EnvVariable != "" {
|
||||
if err = os.Setenv(o.EnvVariable, answer); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = o.IsValidErr()
|
||||
return
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
)
|
||||
|
||||
func TestConfigurable_AddSetting(t *testing.T) {
|
||||
conf := &Configurable{
|
||||
conf := &PluginBase{
|
||||
Settings: Settings{},
|
||||
Label: "TestConfigurable",
|
||||
Name: "TestConfigurable",
|
||||
EnvNamePrefix: "TEST_",
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@ func TestConfigurable_Configure(t *testing.T) {
|
||||
EnvVariable: "TEST_SETTING",
|
||||
Required: true,
|
||||
}
|
||||
conf := &Configurable{
|
||||
conf := &PluginBase{
|
||||
Settings: Settings{setting},
|
||||
Label: "TestConfigurable",
|
||||
Name: "TestConfigurable",
|
||||
}
|
||||
|
||||
_ = os.Setenv("TEST_SETTING", "test_value")
|
||||
@@ -42,9 +42,9 @@ func TestConfigurable_Setup(t *testing.T) {
|
||||
EnvVariable: "TEST_SETTING",
|
||||
Required: false,
|
||||
}
|
||||
conf := &Configurable{
|
||||
conf := &PluginBase{
|
||||
Settings: Settings{setting},
|
||||
Label: "TestConfigurable",
|
||||
Name: "TestConfigurable",
|
||||
}
|
||||
|
||||
err := conf.Setup()
|
||||
25
plugins/tools/converter/html_readability.go
Normal file
25
plugins/tools/converter/html_readability.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package converter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/go-shiori/go-readability"
|
||||
)
|
||||
|
||||
// HtmlReadability Convert HTML input into a clean, readable view
|
||||
// args:
|
||||
//
|
||||
// html (string): full data of web page
|
||||
//
|
||||
// return:
|
||||
//
|
||||
// viewContent (string): html main content
|
||||
// err (error): parser error
|
||||
func HtmlReadability(html string) (ret string, err error) {
|
||||
buf := bytes.NewBufferString(html)
|
||||
var article readability.Article
|
||||
if article, err = readability.FromReader(buf, nil); err != nil {
|
||||
return
|
||||
}
|
||||
ret = article.TextContent
|
||||
return
|
||||
}
|
||||
46
plugins/tools/converter/html_readability_test.go
Normal file
46
plugins/tools/converter/html_readability_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package converter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHtmlReadability(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
html string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "Empty HTML",
|
||||
html: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "HTML with text",
|
||||
html: "<p>Hello World</p>",
|
||||
expected: "Hello World",
|
||||
},
|
||||
{
|
||||
name: "HTML with nested tags",
|
||||
html: "<div><p>Hello</p><p>World</p></div>",
|
||||
expected: "HelloWorld",
|
||||
},
|
||||
{
|
||||
name: "HTML missing tags",
|
||||
html: "<div><p>Hello</p><p>World</div>",
|
||||
expected: "HelloWorld",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := HtmlReadability(tc.html)
|
||||
|
||||
// 验证结果
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
71
plugins/tools/defaults.go
Normal file
71
plugins/tools/defaults.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"github.com/danielmiessler/fabric/plugins/ai"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func NeeDefaults(getVendorsModels func() (*ai.VendorsModels, error)) (ret *Defaults) {
|
||||
vendorName := "Default"
|
||||
ret = &Defaults{
|
||||
PluginBase: &plugins.PluginBase{
|
||||
Name: vendorName,
|
||||
SetupDescription: "Default AI Vendor and Model [required]",
|
||||
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
|
||||
},
|
||||
GetVendorsModels: getVendorsModels,
|
||||
}
|
||||
|
||||
ret.Vendor = ret.AddSetting("Vendor", true)
|
||||
ret.Model = ret.AddSetupQuestionCustom("Model", true,
|
||||
"Enter the index the name of your default model")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Defaults struct {
|
||||
*plugins.PluginBase
|
||||
|
||||
Vendor *plugins.Setting
|
||||
Model *plugins.SetupQuestion
|
||||
GetVendorsModels func() (*ai.VendorsModels, error)
|
||||
}
|
||||
|
||||
func (o *Defaults) Setup() (err error) {
|
||||
var vendorsModels *ai.VendorsModels
|
||||
if vendorsModels, err = o.GetVendorsModels(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
vendorsModels.Print()
|
||||
|
||||
if err = o.Ask(o.Name); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
index, parseErr := strconv.Atoi(o.Model.Value)
|
||||
if parseErr == nil {
|
||||
if o.Vendor.Value, o.Model.Value, err = vendorsModels.GetGroupAndItemByItemNumber(index); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
o.Vendor.Value = vendorsModels.FindGroupsByItemFirst(o.Model.Value)
|
||||
}
|
||||
|
||||
//verify
|
||||
vendorNames := vendorsModels.FindGroupsByItem(o.Model.Value)
|
||||
if len(vendorNames) == 0 {
|
||||
err = errors.Errorf("You need to chose an available default model.")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
o.Vendor.Print()
|
||||
o.Model.Print()
|
||||
|
||||
return
|
||||
}
|
||||
71
plugins/tools/jina/jina.go
Normal file
71
plugins/tools/jina/jina.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package jina
|
||||
|
||||
// see https://jina.ai for more information
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
*plugins.PluginBase
|
||||
ApiKey *plugins.SetupQuestion
|
||||
}
|
||||
|
||||
func NewClient() (ret *Client) {
|
||||
|
||||
label := "Jina AI"
|
||||
|
||||
ret = &Client{
|
||||
PluginBase: &plugins.PluginBase{
|
||||
Name: label,
|
||||
SetupDescription: "Jina AI Service - to grab a webpage as clean, LLM-friendly text",
|
||||
EnvNamePrefix: plugins.BuildEnvVariablePrefix(label),
|
||||
},
|
||||
}
|
||||
|
||||
ret.ApiKey = ret.AddSetupQuestion("API Key", false)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ScrapeURL return the main content of a webpage in clean, LLM-friendly text.
|
||||
func (jc *Client) ScrapeURL(url string) (ret string, err error) {
|
||||
return jc.request(fmt.Sprintf("https://r.jina.ai/%s", url))
|
||||
}
|
||||
|
||||
func (jc *Client) ScrapeQuestion(question string) (ret string, err error) {
|
||||
return jc.request(fmt.Sprintf("https://s.jina.ai/%s", question))
|
||||
}
|
||||
|
||||
func (jc *Client) request(requestURL string) (ret string, err error) {
|
||||
var req *http.Request
|
||||
if req, err = http.NewRequest("GET", requestURL, nil); err != nil {
|
||||
err = fmt.Errorf("error creating request: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// if api keys exist, set the header
|
||||
if jc.ApiKey.Value != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+jc.ApiKey.Value)
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
var resp *http.Response
|
||||
if resp, err = client.Do(req); err != nil {
|
||||
err = fmt.Errorf("error sending request: %w", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var body []byte
|
||||
if body, err = io.ReadAll(resp.Body); err != nil {
|
||||
err = fmt.Errorf("error reading response body: %w", err)
|
||||
return
|
||||
}
|
||||
ret = string(body)
|
||||
return
|
||||
}
|
||||
42
plugins/tools/lang/language.go
Normal file
42
plugins/tools/lang/language.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package lang
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
func NewLanguage() (ret *Language) {
|
||||
|
||||
label := "Language"
|
||||
ret = &Language{}
|
||||
|
||||
ret.PluginBase = &plugins.PluginBase{
|
||||
Name: label,
|
||||
SetupDescription: "Language - Default AI Vendor Output Language",
|
||||
EnvNamePrefix: plugins.BuildEnvVariablePrefix(label),
|
||||
ConfigureCustom: ret.configure,
|
||||
}
|
||||
|
||||
ret.DefaultLanguage = ret.AddSetupQuestionCustom("Output", false,
|
||||
"Enter your default output language (for example: zh_CN)")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Language struct {
|
||||
*plugins.PluginBase
|
||||
DefaultLanguage *plugins.SetupQuestion
|
||||
}
|
||||
|
||||
func (o *Language) configure() error {
|
||||
if o.DefaultLanguage.Value != "" {
|
||||
langTag, err := language.Parse(o.DefaultLanguage.Value)
|
||||
if err == nil {
|
||||
o.DefaultLanguage.Value = langTag.String()
|
||||
} else {
|
||||
o.DefaultLanguage.Value = ""
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package core
|
||||
package tools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -8,8 +8,9 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/danielmiessler/fabric/db"
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"github.com/danielmiessler/fabric/plugins/db/fsdb"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
@@ -17,16 +18,21 @@ import (
|
||||
"github.com/otiai10/copy"
|
||||
)
|
||||
|
||||
func NewPatternsLoader(patterns *db.Patterns) (ret *PatternsLoader) {
|
||||
const DefaultPatternsGitRepoUrl = "https://github.com/danielmiessler/fabric.git"
|
||||
const DefaultPatternsGitRepoFolder = "patterns"
|
||||
|
||||
func NewPatternsLoader(patterns *fsdb.PatternsEntity) (ret *PatternsLoader) {
|
||||
label := "Patterns Loader"
|
||||
ret = &PatternsLoader{
|
||||
Patterns: patterns,
|
||||
Patterns: patterns,
|
||||
loadedFilePath: patterns.BuildFilePath("loaded"),
|
||||
}
|
||||
|
||||
ret.Configurable = &common.Configurable{
|
||||
Label: label,
|
||||
EnvNamePrefix: common.BuildEnvVariablePrefix(label),
|
||||
ConfigureCustom: ret.configure,
|
||||
ret.PluginBase = &plugins.PluginBase{
|
||||
Name: label,
|
||||
SetupDescription: "Patterns - Downloads patterns [required]",
|
||||
EnvNamePrefix: plugins.BuildEnvVariablePrefix(label),
|
||||
ConfigureCustom: ret.configure,
|
||||
}
|
||||
|
||||
ret.DefaultGitRepoUrl = ret.AddSetupQuestionCustom("Git Repo Url", true,
|
||||
@@ -41,11 +47,13 @@ func NewPatternsLoader(patterns *db.Patterns) (ret *PatternsLoader) {
|
||||
}
|
||||
|
||||
type PatternsLoader struct {
|
||||
*common.Configurable
|
||||
Patterns *db.Patterns
|
||||
*plugins.PluginBase
|
||||
Patterns *fsdb.PatternsEntity
|
||||
|
||||
DefaultGitRepoUrl *common.SetupQuestion
|
||||
DefaultFolder *common.SetupQuestion
|
||||
DefaultGitRepoUrl *plugins.SetupQuestion
|
||||
DefaultFolder *plugins.SetupQuestion
|
||||
|
||||
loadedFilePath string
|
||||
|
||||
pathPatternsPrefix string
|
||||
tempPatternsFolder string
|
||||
@@ -58,6 +66,27 @@ func (o *PatternsLoader) configure() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (o *PatternsLoader) IsConfigured() (ret bool) {
|
||||
ret = o.PluginBase.IsConfigured()
|
||||
if ret {
|
||||
if _, err := os.Stat(o.loadedFilePath); os.IsNotExist(err) {
|
||||
ret = false
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *PatternsLoader) Setup() (err error) {
|
||||
if err = o.PluginBase.Setup(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = o.PopulateDB(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PopulateDB downloads patterns from the internet and populates the patterns folder
|
||||
func (o *PatternsLoader) PopulateDB() (err error) {
|
||||
fmt.Printf("Downloading patterns and Populating %s..\n", o.Patterns.Dir)
|
||||
@@ -90,7 +119,7 @@ func (o *PatternsLoader) PersistPatterns() (err error) {
|
||||
if currentPattern.Name() == newPattern.Name() {
|
||||
break
|
||||
}
|
||||
copy.Copy(filepath.Join(o.Patterns.Dir, newPattern.Name()), filepath.Join(newPatternsFolder, newPattern.Name()))
|
||||
err = copy.Copy(filepath.Join(o.Patterns.Dir, newPattern.Name()), filepath.Join(newPatternsFolder, newPattern.Name()))
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -98,30 +127,26 @@ func (o *PatternsLoader) PersistPatterns() (err error) {
|
||||
|
||||
// movePatterns copies the new patterns into the config directory
|
||||
func (o *PatternsLoader) movePatterns() (err error) {
|
||||
os.MkdirAll(o.Patterns.Dir, os.ModePerm)
|
||||
if err = os.MkdirAll(o.Patterns.Dir, os.ModePerm); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
patternsDir := o.tempPatternsFolder
|
||||
if err = o.PersistPatterns(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
copy.Copy(patternsDir, o.Patterns.Dir) // copies the patterns to the config directory
|
||||
if err = copy.Copy(patternsDir, o.Patterns.Dir); err != nil { // copies the patterns to the config directory
|
||||
return
|
||||
}
|
||||
|
||||
//create an empty file to indicate that the patterns have been updated if not exists
|
||||
_, _ = os.Create(o.loadedFilePath)
|
||||
|
||||
err = os.RemoveAll(patternsDir)
|
||||
return
|
||||
}
|
||||
|
||||
// checks if a pattern already exists in the directory
|
||||
// func DoesPatternExistAlready(name string) (bool, error) {
|
||||
// entry := db.Entry{
|
||||
// Label: name,
|
||||
// }
|
||||
// _, err := entry.GetByName()
|
||||
// if err != nil {
|
||||
// return false, err
|
||||
// }
|
||||
// return true, nil
|
||||
// }
|
||||
|
||||
func (o *PatternsLoader) gitCloneAndCopy() (err error) {
|
||||
// Clones the given repository, creating the remote, the local branches
|
||||
// and fetching the objects, everything in memory:
|
||||
@@ -152,10 +177,10 @@ func (o *PatternsLoader) gitCloneAndCopy() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
var changes []db.DirectoryChange
|
||||
var changes []fsdb.DirectoryChange
|
||||
// ... iterates over the commits
|
||||
if err = cIter.ForEach(func(c *object.Commit) (err error) {
|
||||
// Get the files changed in this commit by comparing with its parents
|
||||
// GetApplyVariables the files changed in this commit by comparing with its parents
|
||||
parentIter := c.Parents()
|
||||
if err = parentIter.ForEach(func(parent *object.Commit) (err error) {
|
||||
var patch *object.Patch
|
||||
@@ -167,7 +192,7 @@ func (o *PatternsLoader) gitCloneAndCopy() (err error) {
|
||||
for _, fileStat := range patch.Stats() {
|
||||
if strings.HasPrefix(fileStat.Name, o.pathPatternsPrefix) {
|
||||
dir := filepath.Dir(fileStat.Name)
|
||||
changes = append(changes, db.DirectoryChange{Dir: dir, Timestamp: c.Committer.When})
|
||||
changes = append(changes, fsdb.DirectoryChange{Dir: dir, Timestamp: c.Committer.When})
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -186,7 +211,9 @@ func (o *PatternsLoader) gitCloneAndCopy() (err error) {
|
||||
return changes[i].Timestamp.Before(changes[j].Timestamp)
|
||||
})
|
||||
|
||||
o.makeUniqueList(changes)
|
||||
if err = o.makeUniqueList(changes); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var commit *object.Commit
|
||||
if commit, err = r.CommitObject(ref.Hash()); err != nil {
|
||||
@@ -250,7 +277,7 @@ func (o *PatternsLoader) writeBlobToFile(blob *object.Blob, path string) (err er
|
||||
return
|
||||
}
|
||||
|
||||
func (o *PatternsLoader) makeUniqueList(changes []db.DirectoryChange) {
|
||||
func (o *PatternsLoader) makeUniqueList(changes []fsdb.DirectoryChange) (err error) {
|
||||
uniqueItems := make(map[string]bool)
|
||||
for _, change := range changes {
|
||||
if strings.TrimSpace(change.Dir) != "" && !strings.Contains(change.Dir, "=>") {
|
||||
@@ -271,5 +298,6 @@ func (o *PatternsLoader) makeUniqueList(changes []db.DirectoryChange) {
|
||||
}
|
||||
|
||||
joined := strings.Join(finalList, "\n")
|
||||
os.WriteFile(o.Patterns.UniquePatternsFilePath, []byte(joined), 0o644)
|
||||
err = os.WriteFile(o.Patterns.UniquePatternsFilePath, []byte(joined), 0o644)
|
||||
return
|
||||
}
|
||||
@@ -5,14 +5,16 @@ import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/anaskhan96/soup"
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/api/youtube/v3"
|
||||
"log"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/anaskhan96/soup"
|
||||
"github.com/danielmiessler/fabric/plugins"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/api/youtube/v3"
|
||||
)
|
||||
|
||||
func NewYouTube() (ret *YouTube) {
|
||||
@@ -20,9 +22,10 @@ func NewYouTube() (ret *YouTube) {
|
||||
label := "YouTube"
|
||||
ret = &YouTube{}
|
||||
|
||||
ret.Configurable = &common.Configurable{
|
||||
Label: label,
|
||||
EnvNamePrefix: common.BuildEnvVariablePrefix(label),
|
||||
ret.PluginBase = &plugins.PluginBase{
|
||||
Name: label,
|
||||
SetupDescription: label + " - to grab video transcripts and comments",
|
||||
EnvNamePrefix: plugins.BuildEnvVariablePrefix(label),
|
||||
}
|
||||
|
||||
ret.ApiKey = ret.AddSetupQuestion("API key", true)
|
||||
@@ -31,8 +34,8 @@ func NewYouTube() (ret *YouTube) {
|
||||
}
|
||||
|
||||
type YouTube struct {
|
||||
*common.Configurable
|
||||
ApiKey *common.SetupQuestion
|
||||
*plugins.PluginBase
|
||||
ApiKey *plugins.SetupQuestion
|
||||
|
||||
service *youtube.Service
|
||||
}
|
||||
@@ -61,17 +64,17 @@ func (o *YouTube) GetVideoId(url string) (ret string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (o *YouTube) GrabTranscriptForUrl(url string) (ret string, err error) {
|
||||
func (o *YouTube) GrabTranscriptForUrl(url string, language string) (ret string, err error) {
|
||||
var videoId string
|
||||
if videoId, err = o.GetVideoId(url); err != nil {
|
||||
return
|
||||
}
|
||||
return o.GrabTranscript(videoId)
|
||||
return o.GrabTranscript(videoId, language)
|
||||
}
|
||||
|
||||
func (o *YouTube) GrabTranscript(videoId string) (ret string, err error) {
|
||||
func (o *YouTube) GrabTranscript(videoId string, language string) (ret string, err error) {
|
||||
var transcript string
|
||||
if transcript, err = o.GrabTranscriptBase(videoId); err != nil {
|
||||
if transcript, err = o.GrabTranscriptBase(videoId, language); err != nil {
|
||||
err = fmt.Errorf("transcript not available. (%v)", err)
|
||||
return
|
||||
}
|
||||
@@ -89,14 +92,14 @@ func (o *YouTube) GrabTranscript(videoId string) (ret string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (o *YouTube) GrabTranscriptBase(videoId string) (ret string, err error) {
|
||||
func (o *YouTube) GrabTranscriptBase(videoId string, language string) (ret string, err error) {
|
||||
if err = o.initService(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
url := "https://www.youtube.com/watch?v=" + videoId
|
||||
watchUrl := "https://www.youtube.com/watch?v=" + videoId
|
||||
var resp string
|
||||
if resp, err = soup.Get(url); err != nil {
|
||||
if resp, err = soup.Get(watchUrl); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -117,6 +120,16 @@ func (o *YouTube) GrabTranscriptBase(videoId string) (ret string, err error) {
|
||||
|
||||
if len(captionTracks) > 0 {
|
||||
transcriptURL := captionTracks[0].BaseURL
|
||||
for _, captionTrack := range captionTracks {
|
||||
parsedUrl, error := url.Parse(captionTrack.BaseURL)
|
||||
if error != nil {
|
||||
err = fmt.Errorf("error parsing caption track")
|
||||
}
|
||||
parsedUrlParams, _ := url.ParseQuery(parsedUrl.RawQuery)
|
||||
if parsedUrlParams["lang"][0] == language {
|
||||
transcriptURL = captionTrack.BaseURL
|
||||
}
|
||||
}
|
||||
ret, err = soup.Get(transcriptURL)
|
||||
return
|
||||
}
|
||||
@@ -212,7 +225,7 @@ func (o *YouTube) Grab(url string, options *Options) (ret *VideoInfo, err error)
|
||||
}
|
||||
|
||||
if options.Transcript {
|
||||
if ret.Transcript, err = o.GrabTranscript(videoId); err != nil {
|
||||
if ret.Transcript, err = o.GrabTranscript(videoId, "en"); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
19
restapi/contexts.go
Normal file
19
restapi/contexts.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/plugins/db/fsdb"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ContextsHandler defines the handler for contexts-related operations
|
||||
type ContextsHandler struct {
|
||||
*StorageHandler[fsdb.Context]
|
||||
contexts *fsdb.ContextsEntity
|
||||
}
|
||||
|
||||
// NewContextsHandler creates a new ContextsHandler
|
||||
func NewContextsHandler(r *gin.Engine, contexts *fsdb.ContextsEntity) (ret *ContextsHandler) {
|
||||
ret = &ContextsHandler{
|
||||
StorageHandler: NewStorageHandler[fsdb.Context](r, "contexts", contexts), contexts: contexts}
|
||||
return
|
||||
}
|
||||
35
restapi/patterns.go
Normal file
35
restapi/patterns.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/plugins/db/fsdb"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// PatternsHandler defines the handler for patterns-related operations
|
||||
type PatternsHandler struct {
|
||||
*StorageHandler[fsdb.Pattern]
|
||||
patterns *fsdb.PatternsEntity
|
||||
}
|
||||
|
||||
// NewPatternsHandler creates a new PatternsHandler
|
||||
func NewPatternsHandler(r *gin.Engine, patterns *fsdb.PatternsEntity) (ret *PatternsHandler) {
|
||||
ret = &PatternsHandler{
|
||||
StorageHandler: NewStorageHandler[fsdb.Pattern](r, "patterns", patterns), patterns: patterns}
|
||||
|
||||
// TODO: Add custom, replacement routes here
|
||||
//r.GET("/patterns/:name", ret.Get)
|
||||
return
|
||||
}
|
||||
|
||||
// Get handles the GET /patterns/:name route
|
||||
func (h *PatternsHandler) Get(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
variables := make(map[string]string) // Assuming variables are passed somehow
|
||||
pattern, err := h.patterns.GetApplyVariables(name, variables)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, pattern)
|
||||
}
|
||||
28
restapi/serve.go
Normal file
28
restapi/serve.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/core"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Serve(registry *core.PluginRegistry, address string) (err error) {
|
||||
r := gin.Default()
|
||||
|
||||
// Middleware
|
||||
r.Use(gin.Logger())
|
||||
r.Use(gin.Recovery())
|
||||
|
||||
// Register routes
|
||||
fabricDb := registry.Db
|
||||
NewPatternsHandler(r, fabricDb.Patterns)
|
||||
NewContextsHandler(r, fabricDb.Contexts)
|
||||
NewSessionsHandler(r, fabricDb.Sessions)
|
||||
|
||||
// Start server
|
||||
err = r.Run(address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
19
restapi/sessions.go
Normal file
19
restapi/sessions.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/plugins/db/fsdb"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// SessionsHandler defines the handler for sessions-related operations
|
||||
type SessionsHandler struct {
|
||||
*StorageHandler[fsdb.Session]
|
||||
sessions *fsdb.SessionsEntity
|
||||
}
|
||||
|
||||
// NewSessionsHandler creates a new SessionsHandler
|
||||
func NewSessionsHandler(r *gin.Engine, sessions *fsdb.SessionsEntity) (ret *SessionsHandler) {
|
||||
ret = &SessionsHandler{
|
||||
StorageHandler: NewStorageHandler[fsdb.Session](r, "sessions", sessions), sessions: sessions}
|
||||
return ret
|
||||
}
|
||||
100
restapi/storage.go
Normal file
100
restapi/storage.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/danielmiessler/fabric/plugins/db"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// StorageHandler defines the handler for storage-related operations
|
||||
type StorageHandler[T any] struct {
|
||||
storage db.Storage[T]
|
||||
}
|
||||
|
||||
// NewStorageHandler creates a new StorageHandler
|
||||
func NewStorageHandler[T any](r *gin.Engine, entityType string, storage db.Storage[T]) (ret *StorageHandler[T]) {
|
||||
ret = &StorageHandler[T]{storage: storage}
|
||||
r.GET(fmt.Sprintf("/%s/:name", entityType), ret.Get)
|
||||
r.GET(fmt.Sprintf("/%s/names", entityType), ret.GetNames)
|
||||
r.DELETE(fmt.Sprintf("/%s/:name", entityType), ret.Delete)
|
||||
r.GET(fmt.Sprintf("/%s/exists/:name", entityType), ret.Exists)
|
||||
r.PUT(fmt.Sprintf("/%s/rename/:oldName/:newName", entityType), ret.Rename)
|
||||
r.POST(fmt.Sprintf("/%s/:name", entityType), ret.Save)
|
||||
return
|
||||
}
|
||||
|
||||
// Get handles the GET /storage/:name route
|
||||
func (h *StorageHandler[T]) Get(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
item, err := h.storage.Get(name)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, item)
|
||||
}
|
||||
|
||||
// GetNames handles the GET /storage/names route
|
||||
func (h *StorageHandler[T]) GetNames(c *gin.Context) {
|
||||
names, err := h.storage.GetNames()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, names)
|
||||
}
|
||||
|
||||
// Delete handles the DELETE /storage/:name route
|
||||
func (h *StorageHandler[T]) Delete(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
err := h.storage.Delete(name)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
c.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
// Exists handles the GET /storage/exists/:name route
|
||||
func (h *StorageHandler[T]) Exists(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
exists := h.storage.Exists(name)
|
||||
c.JSON(http.StatusOK, exists)
|
||||
}
|
||||
|
||||
// Rename handles the PUT /storage/rename/:oldName/:newName route
|
||||
func (h *StorageHandler[T]) Rename(c *gin.Context) {
|
||||
oldName := c.Param("oldName")
|
||||
newName := c.Param("newName")
|
||||
err := h.storage.Rename(oldName, newName)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
c.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
// Save handles the POST /storage/save/:name route
|
||||
func (h *StorageHandler[T]) Save(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
|
||||
// Read the request body
|
||||
body := c.Request.Body
|
||||
defer body.Close()
|
||||
|
||||
content, err := io.ReadAll(body)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Save the content to storage
|
||||
err = h.storage.Save(name, content)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
c.Status(http.StatusOK)
|
||||
}
|
||||
112
setup_fabric.bat
Normal file
112
setup_fabric.bat
Normal file
@@ -0,0 +1,112 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
:: Check if running with administrator privileges
|
||||
net session >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo Please run this script as an administrator.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: Install Chocolatey (package manager for Windows)
|
||||
if not exist "%ProgramData%\chocolatey\bin\choco.exe" (
|
||||
echo Installing Chocolatey...
|
||||
@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "[System.Net.ServicePointManager]::SecurityProtocol = 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
|
||||
)
|
||||
|
||||
:: Install Go
|
||||
where go >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo Installing Go...
|
||||
choco install golang -y
|
||||
set "PATH=%PATH%;C:\Program Files\Go\bin"
|
||||
)
|
||||
|
||||
:: Install Git
|
||||
where git >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo Installing Git...
|
||||
choco install git -y
|
||||
)
|
||||
|
||||
:: Refresh environment variables
|
||||
call refreshenv
|
||||
|
||||
:: Install Fabric
|
||||
echo Installing Fabric...
|
||||
go install github.com/danielmiessler/fabric@latest
|
||||
|
||||
:: Run Fabric setup
|
||||
echo Running Fabric setup...
|
||||
fabric --setup
|
||||
|
||||
:: Install yt helper
|
||||
echo Installing yt helper...
|
||||
go install github.com/danielmiessler/yt@latest
|
||||
|
||||
:: Prompt user for YouTube API Key
|
||||
set /p YOUTUBE_API_KEY=Enter your YouTube API Key (press Enter to skip):
|
||||
if not "!YOUTUBE_API_KEY!"=="" (
|
||||
echo YOUTUBE_API_KEY=!YOUTUBE_API_KEY!>> %USERPROFILE%\.config\fabric\.env
|
||||
)
|
||||
|
||||
:: Prompt user for OpenAI API Key
|
||||
set /p OPENAI_API_KEY=Enter your OpenAI API Key (press Enter to skip):
|
||||
if not "!OPENAI_API_KEY!"=="" (
|
||||
echo OPENAI_API_KEY=!OPENAI_API_KEY!>> %USERPROFILE%\.config\fabric\.env
|
||||
)
|
||||
|
||||
:: Run Fabric
|
||||
:run_fabric
|
||||
cls
|
||||
echo Fabric is now installed and ready to use.
|
||||
echo.
|
||||
echo Available options:
|
||||
echo 1. Run Fabric with custom options
|
||||
echo 2. List patterns
|
||||
echo 3. List models
|
||||
echo 4. Update patterns
|
||||
echo 5. Exit
|
||||
echo.
|
||||
set /p CHOICE=Enter your choice (1-5):
|
||||
|
||||
if "%CHOICE%"=="1" (
|
||||
set /p PATTERN=Enter pattern (or press Enter to skip):
|
||||
set /p CONTEXT=Enter context (or press Enter to skip):
|
||||
set /p SESSION=Enter session (or press Enter to skip):
|
||||
set /p MODEL=Enter model (or press Enter to skip):
|
||||
set /p TEMPERATURE=Enter temperature (or press Enter for default):
|
||||
set /p STREAM=Do you want to stream output? (Y/N):
|
||||
|
||||
set "FABRIC_CMD=fabric"
|
||||
if not "!PATTERN!"=="" set "FABRIC_CMD=!FABRIC_CMD! --pattern !PATTERN!"
|
||||
if not "!CONTEXT!"=="" set "FABRIC_CMD=!FABRIC_CMD! --context !CONTEXT!"
|
||||
if not "!SESSION!"=="" set "FABRIC_CMD=!FABRIC_CMD! --session !SESSION!"
|
||||
if not "!MODEL!"=="" set "FABRIC_CMD=!FABRIC_CMD! --model !MODEL!"
|
||||
if not "!TEMPERATURE!"=="" set "FABRIC_CMD=!FABRIC_CMD! --temperature !TEMPERATURE!"
|
||||
if /i "!STREAM!"=="Y" set "FABRIC_CMD=!FABRIC_CMD! --stream"
|
||||
|
||||
echo Running Fabric with command: !FABRIC_CMD!
|
||||
!FABRIC_CMD!
|
||||
pause
|
||||
goto run_fabric
|
||||
) else if "%CHOICE%"=="2" (
|
||||
fabric --listpatterns
|
||||
pause
|
||||
goto run_fabric
|
||||
) else if "%CHOICE%"=="3" (
|
||||
fabric --listmodels
|
||||
pause
|
||||
goto run_fabric
|
||||
) else if "%CHOICE%"=="4" (
|
||||
fabric --updatepatterns
|
||||
pause
|
||||
goto run_fabric
|
||||
) else if "%CHOICE%"=="5" (
|
||||
exit /b 0
|
||||
) else (
|
||||
echo Invalid choice. Please try again.
|
||||
pause
|
||||
goto run_fabric
|
||||
)
|
||||
3
version.go
Normal file
3
version.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v1.4.71"
|
||||
Reference in New Issue
Block a user