Compare commits

...

100 Commits

Author SHA1 Message Date
github-actions[bot]
666a1d32a3 Update version to v1.4.109 and commit 2024-11-24 15:31:52 +00:00
Eugen Eisler
4ed512b8d4 Merge pull request #1157 from mattjoyce/curly-brace-templates
fix: process template variables in raw input
2024-11-24 16:30:55 +01:00
Matt Joyce
af16494be1 fax: raw mode was doubling user input, because it's now already embeded in pattern
streamlined some context staging
2024-11-23 10:45:38 +11:00
Matt Joyce
9afa397c27 fix : template.go will handle missing var in stdin imput too.
echo 'Hello {{name}}' | ./fabric -v=noname:World
missing required variable: name
2024-11-23 08:34:58 +11:00
Matt Joyce
58f9d3c89c fix: process template variables in raw input
Process template variables ({{var}}) consistently in both pattern files
and raw input messages. Previously variables were only processed when
using pattern files.

- Add template variable processing for raw input in BuildSession
- Initialize messageContent explicitly
- Remove errantly committed build artifact (fabric binary in previous commit)
2024-11-23 08:27:56 +11:00
Daniel Miessler
7732b6fe55 Added analyze_mistakes. 2024-11-22 10:01:40 -08:00
github-actions[bot]
0d5f15edda Update version to v1.4.108 and commit 2024-11-21 21:57:24 +00:00
Eugen Eisler
4e2aa1b6d8 Merge pull request #1155 from mattjoyce/curly-brace-templates
Curly brace templates and plugins
2024-11-21 22:56:37 +01:00
Matt Joyce
b6eb969b3a feat(template): implement core plugin system and utility plugins
Add initial set of utility plugins for the template system:
- datetime: Date/time formatting and manipulation
- fetch: HTTP content retrieval and processing
- file: File system operations and content handling
- sys: System information and environment access
- text: String manipulation and formatting operations

Each plugin includes:
- Implementation with comprehensive test coverage
- Markdown documentation of capabilities
- Integration with template package

This builds on the template system to provide practical utility functions
while maintaining a focused scope for the initial plugin release.
2024-11-21 14:27:22 +11:00
github-actions[bot]
4c22965f4b Update version to v1.4.107 and commit 2024-11-19 22:02:03 +00:00
Eugen Eisler
7d28c95f48 ci: update patterns zip workflow 2024-11-19 23:01:05 +01:00
Eugen Eisler
94b713e3a5 ci: remove patterns zip workflow 2024-11-19 22:53:57 +01:00
Eugen Eisler
dccc92e8e0 Merge pull request #1149 from mathisto/patch-1
Fix typo in md_callout
2024-11-19 21:35:24 +01:00
Matt Kelly
590a9e452d Fix typo in md_callout
Just a small typo in this pattern. Thanks so much for this splendid tool.
2024-11-19 12:09:25 -05:00
github-actions[bot]
56322aaeb5 Update version to v1.4.106 and commit 2024-11-19 12:13:12 +00:00
Eugen Eisler
3684031f44 feat: migrate to official anthropics Go SDK 2024-11-19 13:12:10 +01:00
github-actions[bot]
005f2b7db5 Update version to v1.4.105 and commit 2024-11-19 08:55:31 +00:00
Eugen Eisler
67840605fc Merge pull request #1147 from mattjoyce/adhoc-pattern-feature
refactor: unify pattern loading and variable handling
2024-11-19 09:54:47 +01:00
Matt Joyce
d475e7b568 feat(template): introduce template package for variable substitution
- Add new template package to handle variable substitution with {{variable}} syntax
- Move substitution logic from patterns to centralized template system
- Update patterns.go to use template package for variable processing
- Support special {{input}} handling for pattern content
- Update chatter.go and rest API to pass input parameter
- Enable multiple passes to handle nested variables
- Report errors for missing required variables

This change sets up a foundation for future templating features like front matter
and plugin support while keeping the substitution logic centralized.
2024-11-19 16:57:14 +11:00
Matt Joyce
1f07ea25a2 refactor: unify pattern loading and variable handling
- Stronger separation of concerns between chatter.go and patterns.go
- Consolidate pattern loading logic into GetPattern method
- Support both file and database patterns through single interface
- Maintain API compatibility with Storage interface
- Handle variable substitution in one place
- Keep backward compatibility for REST API through Get method

The changes enable cleaner pattern handling while maintaining
existing interfaces and adding file-based pattern support.
2024-11-19 10:31:06 +11:00
Eugen Eisler
08f4e28342 Merge pull request #1146 from mrwadams/patch-1
Add summarize_meeting
2024-11-18 23:22:42 +01:00
github-actions[bot]
97666d9537 Update version to v1.4.104 and commit 2024-11-18 22:21:31 +00:00
Eugen Eisler
f7733f932b Merge pull request #1142 from mattjoyce/adhoc-pattern-feature
feat: add file-based pattern support
2024-11-18 23:20:34 +01:00
Matt Adams
20a039a8ab Add summarize_meeting
# What this Pull Request (PR) does

Add a new pattern to create a meeting summary from an audio transcript.

The pattern outputs the following sections (where relevant):
- Key Points
- Tasks
- Decisions
- Next Steps
2024-11-18 20:37:47 +00:00
github-actions[bot]
29856e4749 Update version to v1.4.103 and commit 2024-11-18 09:13:56 +00:00
Eugen Eisler
47a797e884 Merge pull request #1133 from igophper/fix_gin_logger
fix: fix default gin
2024-11-18 10:13:12 +01:00
Eugen Eisler
d4079aa543 Merge pull request #1129 from xyb/screenshoot
add a screenshot of fabric
2024-11-18 10:12:59 +01:00
github-actions[bot]
62eb837422 Update version to v1.4.102 and commit 2024-11-18 09:12:25 +00:00
Eugen Eisler
8d81f8d3aa Merge pull request #1143 from mariozig/patch-1
Update docker image
2024-11-18 10:11:39 +01:00
Mario Zigliotto
e8acf9ca07 Update docker image 2024-11-17 10:48:38 -08:00
Matt Joyce
af4752d324 feat: add file-based pattern support
Allow patterns to be loaded directly from files using explicit path prefixes
(~/, ./, /, or \). This enables easier testing and iteration of patterns
without requiring installation into the fabric config structure.

- Supports relative paths (./pattern.txt, ../pattern.txt)
- Supports home directory expansion (~/patterns/test.txt)
- Supports absolute paths
- Maintains backwards compatibility with named patterns
- Requires explicit path markers to distinguish from pattern names

Example usage:
  fabric --pattern ./draft-pattern.txt
  fabric --pattern ~/patterns/my-pattern.txt
  fabric --pattern ../../shared-patterns/test.txt
2024-11-17 14:39:49 +11:00
github-actions[bot]
fbd1fbfc67 Update version to v1.4.101 and commit 2024-11-15 16:04:16 +00:00
Eugen Eisler
d1fe826f14 improve logging for missing setup steps 2024-11-15 17:03:13 +01:00
江杭辉
2ae26dc2a6 fix: fix default gin 2024-11-13 18:02:57 +08:00
Xie Yanbo
c396288ca7 add a screenshot of fabric 2024-11-13 11:37:01 +08:00
github-actions[bot]
125e7a341f Update version to v1.4.100 and commit 2024-11-13 02:19:32 +00:00
Daniel Miessler
064ab9ba85 Added our first formal stitch. 2024-11-12 18:18:44 -08:00
Daniel Miessler
f0ee8287a7 Upgraded AI result rater. 2024-11-10 22:53:14 -08:00
Daniel Miessler
47ccc33dfc Upgraded AI result rater. 2024-11-10 22:48:18 -08:00
Daniel Miessler
ceb735482a Upgraded AI result rater. 2024-11-10 22:46:10 -08:00
Daniel Miessler
473a20c0f6 Upgraded AI result rater. 2024-11-10 22:44:21 -08:00
Daniel Miessler
a337e81a81 Upgraded AI result rater. 2024-11-10 22:36:06 -08:00
Daniel Miessler
7d773b51d0 Upgraded AI result rater. 2024-11-10 22:28:26 -08:00
Daniel Miessler
bca10ddf7c Upgraded AI result rater. 2024-11-10 22:25:00 -08:00
Daniel Miessler
9756c575f3 Upgraded AI result rater. 2024-11-10 22:22:04 -08:00
Daniel Miessler
d02fb3e34d Upgraded AI result rater. 2024-11-10 22:17:37 -08:00
github-actions[bot]
988ff88a15 Update version to v1.4.99 and commit 2024-11-10 18:45:23 +00:00
Eugen Eisler
5de85c3da5 Merge pull request #1126 from jaredmontoya/fix-nix-package
flake: add gomod2nix auto-update
2024-11-10 19:43:55 +01:00
Daniel Miessler
5907f9dbac Upgraded AI result rater. 2024-11-09 22:14:31 -08:00
Daniel Miessler
1293e37525 Upgraded AI result rater. 2024-11-09 21:14:30 -08:00
Daniel Miessler
0a55e6c742 Upgraded AI result rater. 2024-11-09 18:41:18 -08:00
Daniel Miessler
ff3b18485f Upgraded AI result rater. 2024-11-09 18:39:25 -08:00
Daniel Miessler
2fec6e2e52 Upgraded AI result rater. 2024-11-09 18:37:18 -08:00
Daniel Miessler
9250f19d15 Upgraded AI result rater. 2024-11-09 18:30:09 -08:00
Daniel Miessler
1e7c5c3b6a Upgraded AI result rater. 2024-11-09 18:18:40 -08:00
jaredmontoya
0289b67a84 flake: add gomod2nix auto-update 2024-11-09 17:27:13 +01:00
github-actions[bot]
8934dbaa42 Update version to v1.4.98 and commit 2024-11-09 12:08:16 +00:00
Eugen Eisler
75c3d7ea6a ci: zip patterns 2024-11-09 13:07:52 +01:00
github-actions[bot]
a94ad620bc Update version to v1.4.97 and commit 2024-11-09 12:01:59 +00:00
Eugen Eisler
c6ca1a60d1 feat: update dependencies; improve vendors setup/default model 2024-11-09 13:01:44 +01:00
github-actions[bot]
4321c9d518 Update version to v1.4.96 and commit 2024-11-09 10:51:05 +00:00
Eugen Eisler
6cd86639ce feat: add claude-3-5-haiku-latest model 2024-11-09 11:50:48 +01:00
Daniel Miessler
3e6ad1029c Merge pull request #1060 from noamsiegel/Analyze-Candidates-Pattern
Analyze Candidates Pattern
2024-11-08 21:25:48 -08:00
github-actions[bot]
1cf967582d Update version to v1.4.95 and commit 2024-11-09 05:25:27 +00:00
Daniel Miessler
5e1b4e87e7 Merge pull request #1122 from Selemela07/patch-1
Create Selemela07 devcontainer.json
2024-11-08 21:25:11 -08:00
Daniel Miessler
76d6788231 Merge pull request #1123 from polyglotdev/correct-obsidian-shell-script
 Added unaliasing to pattern setup
2024-11-08 21:24:22 -08:00
Daniel Miessler
73a0e38af6 Merge branch 'main' of github.com:danielmiessler/fabric 2024-11-08 21:19:17 -08:00
Daniel Miessler
ff0ee4f111 Updated README. 2024-11-08 21:19:14 -08:00
Dom Hallan
de61e56fda Added unaliasing to pattern setup
In the process of setting up patterns, we've added a step to unalias any existing alias with the same name. This ensures that our dynamically defined functions won't conflict with any pre-existing aliases.
2024-11-08 11:11:25 -05:00
Selemela07
79b03c681a Create Selemela07 devcontainer.json
Edit, patch diff
2024-11-08 03:35:50 +02:00
Eugen Eisler
b8de34e539 Merge pull request #1119 from verebes1/add-dynamic-file-saving
Add auto save functionality
2024-11-07 13:13:35 +01:00
David
6377f951d8 Add auto save to aliases
-Updated the readme with information about autogenerating aliases that
allow autosaving to obsidian like tools
-Updated the table of contents
2024-11-07 09:15:48 +00:00
github-actions[bot]
86b702bf46 Update version to v1.4.94 and commit 2024-11-06 22:28:45 +00:00
Eugen Eisler
232847b218 Merge pull request #1108 from butterflyx/fix/yt-shorts
[add] RegEx for YT shorts
2024-11-06 23:28:29 +01:00
Eugen Eisler
44dae97784 Merge pull request #1117 from verebes1/add-aliases-for-patterns
Add alias generation information
2024-11-06 23:28:06 +01:00
Eugen Eisler
d8e3860e49 Merge pull request #1115 from ignacio-arce/main
Added create_diy
2024-11-06 23:24:44 +01:00
Eugen Eisler
e01a84b21d Merge branch 'main' into fix/yt-shorts 2024-11-06 23:22:52 +01:00
David
97c5341bc1 Merge branch 'main' into add-aliases-for-patterns 2024-11-06 22:18:29 +00:00
github-actions[bot]
de30df446d Update version to v1.4.93 and commit 2024-11-06 20:54:53 +00:00
Eugen Eisler
b5b45c8474 fix: short YouTube url patter 2024-11-06 21:54:36 +01:00
David
c5483276e5 Add alias generation information
-Updated the readme with information about generating aliases for each
prompt including on for youtube transcripts
-Updated the table of contents
2024-11-06 15:15:14 +00:00
Ignacio Arce
263b1cb187 Added create_diy 2024-11-06 07:58:24 -03:00
butterflyx
203add15e5 [add] VideoID for YT shorts 2024-11-05 19:55:45 +01:00
github-actions[bot]
b98316a705 Update version to v1.4.92 and commit 2024-11-05 12:50:54 +00:00
Eugen Eisler
f2d9e0e8ea Merge pull request #1109 from leonsgithub/main
Add docker
2024-11-05 13:50:37 +01:00
github-actions[bot]
f5abaac8b7 Update version to v1.4.91 and commit 2024-11-05 10:25:55 +00:00
Eugen Eisler
0bb4f58222 fix: bufio.Scanner message too long 2024-11-05 11:25:32 +01:00
leon
4453afba89 Add docker 2024-11-04 18:56:55 +01:00
github-actions[bot]
96c8117135 Update version to v1.4.90 and commit 2024-11-04 12:44:39 +00:00
Eugen Eisler
1830ae2321 feat: impl. Youtube PlayList support 2024-11-04 13:44:20 +01:00
Eugen Eisler
7af94a9d2a fix: close #1103, Update Readme hpt to install to_pdf 2024-11-04 10:54:26 +01:00
github-actions[bot]
6d10c26c5d Update version to v1.4.89 and commit 2024-11-04 09:48:39 +00:00
Eugen Eisler
681f1a49a5 fix: close #1106, fix pipe reading 2024-11-04 10:48:22 +01:00
Eugen Eisler
b750171593 feat: YouTube PlayList support 2024-11-04 10:48:22 +01:00
Eugen Eisler
02a019632b Merge pull request #1102 from jholsgrove/main
Create user story pattern
2024-10-31 19:02:19 +01:00
Justin Holsgrove
385d381cf1 create user story pattern 2024-10-31 13:19:12 +00:00
github-actions[bot]
48e8d76f21 Update version to v1.4.88 and commit 2024-10-30 19:58:26 +00:00
Eugen Eisler
d5336b2796 Merge pull request #1098 from jaredmontoya/fix-nix-package-update-ci
Fix nix package update workflow
2024-10-30 20:58:12 +01:00
jaredmontoya
cb1b2bf5ca fix nix package version auto update workflow 2024-10-30 20:51:25 +01:00
Noam Siegel
7139ad013d Added system and user prompts 2024-10-21 12:17:58 -07:00
56 changed files with 3413 additions and 362 deletions

View File

@@ -0,0 +1,5 @@
{
"image": "mcr.microsoft.com/devcontainers/universal:2",
"features": {
}
}

6
.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
.git
.gitignore
.env
README.md
docker-compose.yml
Dockerfile

33
.github/workflows/patterns.yaml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Patterns Artifact
on:
push:
paths:
- "patterns/**" # Trigger only on changes to files in the patterns folder
jobs:
zip-and-upload:
name: Zip and Upload Patterns Folder
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Verify Changes in Patterns Folder
run: |
git fetch origin
if git diff --quiet HEAD~1 -- patterns; then
echo "No changes detected in patterns folder."
exit 1
fi
- name: Zip the Patterns Folder
run: zip -r patterns.zip patterns/
- name: Upload Patterns Artifact
uses: actions/upload-artifact@v3
with:
name: patterns
path: patterns.zip

View File

@@ -21,6 +21,12 @@ jobs:
with:
fetch-depth: 0
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
- name: Setup Nix Cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: Set up Git
run: |
git config user.name "github-actions[bot]"
@@ -41,7 +47,10 @@ jobs:
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}"
new_version="${major}.${minor}.${new_patch}"
new_tag="v${new_version}"
echo "New version is: $new_version"
echo "new_version=$new_version" >> $GITHUB_ENV # Save the new version to environment file
echo "New tag is: $new_tag"
echo "new_tag=$new_tag" >> $GITHUB_ENV # Save the new tag to environment file
@@ -53,11 +62,17 @@ jobs:
- name: Update version.nix file
run: |
echo "\"${{ env.new_tag }}\"" > pkgs/fabric/version.nix
echo "\"${{ env.new_version }}\"" > pkgs/fabric/version.nix
- name: Update gomod2nix.toml file
run: |
nix run .#gomod2nix
- name: Commit changes
run: |
git add version.go
git add pkgs/fabric/version.nix
git add gomod2nix.toml
if ! git diff --staged --quiet; then
git commit -m "Update version to ${{ env.new_tag }} and commit $commit_hash"
else

41
Dockerfile Normal file
View File

@@ -0,0 +1,41 @@
# Use official golang image as builder
FROM golang:1.23.3-alpine AS builder
# Set working directory
WORKDIR /app
# Copy go mod and sum files
COPY go.mod go.sum ./
# Download dependencies
RUN go mod download
# Copy source code
COPY . .
# Build the application
RUN CGO_ENABLED=0 GOOS=linux go build -o fabric
# Use scratch as final base image
FROM alpine:latest
# Copy the binary from builder
COPY --from=builder /app/fabric /fabric
# Copy patterns directory
COPY patterns /patterns
# Ensure clean config directory and copy ENV file
RUN rm -rf /root/.config/fabric && \
mkdir -p /root/.config/fabric
COPY ENV /root/.config/fabric/.env
# Add debug commands
RUN ls -la /root/.config/fabric/
# Expose port 8080
EXPOSE 8080
# Run the binary with debug output
ENTRYPOINT ["/fabric"]
CMD ["--serve"]

9
ENV Normal file
View File

@@ -0,0 +1,9 @@
DEFAULT_VENDOR=OpenRouter
DEFAULT_MODEL=openai/gpt-3.5-turbo-0125
DEFAULT_MODEL_CONTEXT_LENGTH=128K
PATTERNS_LOADER_GIT_REPO_URL=https://github.com/danielmiessler/fabric.git
PATTERNS_LOADER_GIT_REPO_PATTERNS_FOLDER=patterns
OPENROUTER_API_KEY=sk-or-v1-
OPENROUTER_API_BASE_URL=https://openrouter.ai/api/v1
YOUTUBE_API_KEY=AIzaS
JINA_AI_API_KEY=jina_57

140
README.md
View File

@@ -25,51 +25,48 @@
[Helper Apps](#helper-apps) •
[Meta](#meta)
![Screenshot of fabric](images/fabric-summarize.png)
</div>
## Navigation
- [Updates](#updates)
- [What and Why](#what-and-why)
- [Philosophy](#philosophy)
- [Breaking problems into components](#breaking-problems-into-components)
- [Too many prompts](#too-many-prompts)
- [The Fabric approach to prompting](#our-approach-to-prompting)
- [Installation](#Installation)
- [Migration](#Migration)
- [Upgrading](#Upgrading)
- [Usage](#Usage)
- [Examples](#examples)
- [`fabric`](#fabric)
- [Navigation](#navigation)
- [Updates](#updates)
- [Intro videos](#intro-videos)
- [What and why](#what-and-why)
- [Philosophy](#philosophy)
- [Breaking problems into components](#breaking-problems-into-components)
- [Too many prompts](#too-many-prompts)
- [Installation](#installation)
- [Get Latest Release Binaries](#get-latest-release-binaries)
- [From Source](#from-source)
- [Environment Variables](#environment-variables)
- [Setup](#setup)
- [Add aliases for all patterns](#add-aliases-for-all-patterns)
- [Save your files in markdown using aliases](#save-your-files-in-markdown-using-aliases)
- [Migration](#migration)
- [Upgrading](#upgrading)
- [Usage](#usage)
- [Our approach to prompting](#our-approach-to-prompting)
- [Examples](#examples)
- [Just use the Patterns](#just-use-the-patterns)
- [Custom Patterns](#custom-patterns)
- [Helper Apps](#helper-apps)
- [pbpaste](#pbpaste)
- [Meta](#meta)
- [Primary contributors](#primary-contributors)
- [Custom Patterns](#custom-patterns)
- [Helper Apps](#helper-apps)
- [`to_pdf`](#to_pdf)
- [`to_pdf` Installation](#to_pdf-installation)
- [pbpaste](#pbpaste)
- [Meta](#meta)
- [Primary contributors](#primary-contributors)
<br />
## 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`
> * You can now use (-S) to configure an Ollama server.
> * **We're working on a GUI rewrite in Go as well**
November 8, 2024
> * **Multimodal Support**: You can now us `-a` (attachment) for Multimodal submissions to OpenAI models that support it. Example: `fabric -a https://path/to/image "Give me a description of this image."`
## Intro videos
Keep in mind that many of these were recorded when Fabric was Python-based, so remember to use the current [install instructions](#Installation) below.
* [Network Chuck](https://www.youtube.com/watch?v=UbDyjIIGaxQ)
* [David Bombal](https://www.youtube.com/watch?v=vF-MQmVxnCs)
* [My Own Intro to the Tool](https://www.youtube.com/watch?v=wPEyyigh10g)
* [More Fabric YouTube Videos](https://www.youtube.com/results?search_query=fabric+ai)
## What and why
@@ -81,6 +78,15 @@ Since the start of 2023 and GenAI we've seen a massive number of AI applications
Fabric was created to address this by enabling everyone to granularly apply AI to everyday challenges.
## Intro videos
Keep in mind that many of these were recorded when Fabric was Python-based, so remember to use the current [install instructions](#Installation) below.
* [Network Chuck](https://www.youtube.com/watch?v=UbDyjIIGaxQ)
* [David Bombal](https://www.youtube.com/watch?v=vF-MQmVxnCs)
* [My Own Intro to the Tool](https://www.youtube.com/watch?v=wPEyyigh10g)
* [More Fabric YouTube Videos](https://www.youtube.com/results?search_query=fabric+ai)
## Philosophy
> AI isn't a thing; it's a _magnifier_ of a thing. And that thing is **human creativity**.
@@ -174,6 +180,72 @@ fabric --setup
```
If everything works you are good to go.
### Add aliases for all patterns
In order to add aliases for all your patterns and use them directly as commands ie. `summarize` instead of `fabric --pattern summarize`
You can add the following to your `.zshrc` or `.bashrc` file.
```bash
# Loop through all files in the ~/.config/fabric/patterns directory
for pattern_file in $HOME/.config/fabric/patterns/*; do
# Get the base name of the file (i.e., remove the directory path)
pattern_name=$(basename "$pattern_file")
# Create an alias in the form: alias pattern_name="fabric --pattern pattern_name"
alias_command="alias $pattern_name='fabric --pattern $pattern_name'"
# Evaluate the alias command to add it to the current shell
eval "$alias_command"
done
yt() {
local video_link="$1"
fabric -y "$video_link" --transcript
}
```
This also creates a `yt` alias that allows you to use `yt https://www.youtube.com/watch?v=4b0iet22VIk` to get your transcripts.
#### Save your files in markdown using aliases
If in addition to the above aliases you would like to have the option to save the output to your favourite markdown note vault like Obsidian then instead of the above add the following to your `.zshrc` or `.bashrc` file:
```bash
# Define the base directory for Obsidian notes
obsidian_base="/path/to/obsidian"
# Loop through all files in the ~/.config/fabric/patterns directory
for pattern_file in ~/.config/fabric/patterns/*; do
# Get the base name of the file (i.e., remove the directory path)
pattern_name=$(basename "$pattern_file")
# Unalias any existing alias with the same name
unalias "$pattern_name" 2>/dev/null
# Define a function dynamically for each pattern
eval "
$pattern_name() {
local title=\$1
local date_stamp=\$(date +'%Y-%m-%d')
local output_path=\"\$obsidian_base/\${date_stamp}-\${title}.md\"
# Check if a title was provided
if [ -n \"\$title\" ]; then
# If a title is provided, use the output path
fabric --pattern \"$pattern_name\" -o \"\$output_path\"
else
# If no title is provided, use --stream
fabric --pattern \"$pattern_name\" --stream
fi
}
"
done
yt() {
local video_link="$1"
fabric -y "$video_link" --transcript
}
```
This will allow you to use the patterns as aliases like in the above for example `summarize` instead of `fabric --pattern summarize --stream`, however if you pass in an extra argument like this `summarize "my_article_title"` your output will be saved in the destination that you set in `obsidian_base="/path/to/obsidian"` in the following format `YYYY-MM-DD-my_article_title.md` where the date gets autogenerated for you.
You can tweak the date format by tweaking the `date_stamp` format.
### Migration
@@ -359,7 +431,7 @@ 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@latest
go install github.com/danielmiessler/fabric/plugins/tools/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.

View File

@@ -2,6 +2,7 @@ package cli
import (
"fmt"
"github.com/danielmiessler/fabric/plugins/tools/youtube"
"os"
"path/filepath"
"strconv"
@@ -144,37 +145,38 @@ func Cli(version string) (err error) {
}
var videoId string
if videoId, err = registry.YouTube.GetVideoId(currentFlags.YouTube); err != nil {
var playlistId string
if videoId, playlistId, err = registry.YouTube.GetVideoOrPlaylistId(currentFlags.YouTube); err != nil {
return
} else if (videoId == "" || currentFlags.YouTubePlaylist) && playlistId != "" {
if currentFlags.Output != "" {
err = registry.YouTube.FetchAndSavePlaylist(playlistId, currentFlags.Output)
} else {
var videos []*youtube.VideoMeta
if videos, err = registry.YouTube.FetchPlaylistVideos(playlistId); err != nil {
err = fmt.Errorf("error fetching playlist videos: %v", err)
return
}
for _, video := range videos {
var message string
if message, err = processYoutubeVideo(currentFlags, registry, video.Id); err != nil {
return
}
if !currentFlags.IsChatRequest() {
if err = WriteOutput(message, fmt.Sprintf("%v.md", video.TitleNormalized)); err != nil {
return
}
} else {
messageTools = AppendMessage(messageTools, message)
}
}
}
return
}
if !currentFlags.YouTubeComments || currentFlags.YouTubeTranscript {
var transcript string
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
}
messageTools = AppendMessage(messageTools, transcript)
}
if currentFlags.YouTubeComments {
var comments []string
if comments, err = registry.YouTube.GrabComments(videoId); err != nil {
return
}
commentsString := strings.Join(comments, "\n")
messageTools = AppendMessage(messageTools, commentsString)
}
messageTools, err = processYoutubeVideo(currentFlags, registry, videoId)
if !currentFlags.IsChatRequest() {
err = currentFlags.WriteOutput(messageTools)
return
@@ -254,3 +256,43 @@ func Cli(version string) (err error) {
}
return
}
func processYoutubeVideo(
flags *Flags, registry *core.PluginRegistry, videoId string) (message string, err error) {
if !flags.YouTubeComments || flags.YouTubeTranscript {
var transcript string
var language = "en"
if flags.Language != "" || registry.Language.DefaultLanguage.Value != "" {
if flags.Language != "" {
language = flags.Language
} else {
language = registry.Language.DefaultLanguage.Value
}
}
if transcript, err = registry.YouTube.GrabTranscript(videoId, language); err != nil {
return
}
message = AppendMessage(message, transcript)
}
if flags.YouTubeComments {
var comments []string
if comments, err = registry.YouTube.GrabComments(videoId); err != nil {
return
}
commentsString := strings.Join(comments, "\n")
message = AppendMessage(message, commentsString)
}
return
}
func WriteOutput(message string, outputFile string) (err error) {
fmt.Println(message)
if outputFile != "" {
err = CreateOutputFile(message, outputFile)
}
return
}

View File

@@ -42,7 +42,8 @@ type Flags struct {
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"`
YouTube string `short:"y" long:"youtube" description:"YouTube video or play list \"URL\" to grab transcript, comments from it and send to chat or print it put to the console and store it in the output file"`
YouTubePlaylist bool `long:"playlist" description:"Prefer playlist over video if both ids are present in the URL"`
YouTubeTranscript bool `long:"transcript" description:"Grab transcript from YouTube video and send to chat (it 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:""`
@@ -72,16 +73,19 @@ func Init() (ret *Flags, err error) {
}
info, _ := os.Stdin.Stat()
hasStdin := (info.Mode() & os.ModeCharDevice) == 0
pipedToStdin := (info.Mode() & os.ModeCharDevice) == 0
// takes input from stdin if it exists, otherwise takes input from args (the last argument)
if hasStdin {
if pipedToStdin {
//fmt.Printf("piped: %v\n", args)
if message, err = readStdin(); err != nil {
return
}
} else if len(args) > 0 {
//fmt.Printf("no piped: %v\n", args)
message = args[len(args)-1]
} else {
//fmt.Printf("no data: %v\n", args)
message = ""
}
ret.Message = message
@@ -90,20 +94,21 @@ func Init() (ret *Flags, err error) {
}
// readStdin reads from stdin and returns the input as a string or an error
func readStdin() (string, error) {
func readStdin() (ret string, err error) {
reader := bufio.NewReader(os.Stdin)
var input string
var sb strings.Builder
for {
line, err := reader.ReadString('\n')
if err != nil {
if errors.Is(err, io.EOF) {
sb.WriteString(line)
break
}
return "", fmt.Errorf("error reading from stdin: %w", err)
return "", fmt.Errorf("error reading piped message from stdin: %w", err)
}
input += line
sb.WriteString(line)
}
return input, nil
return sb.String(), nil
}
func (o *Flags) BuildChatOptions() (ret *common.ChatOptions) {

View File

@@ -10,6 +10,7 @@ import (
"github.com/danielmiessler/fabric/common"
"github.com/danielmiessler/fabric/plugins/ai"
"github.com/danielmiessler/fabric/plugins/db/fsdb"
"github.com/danielmiessler/fabric/plugins/template"
)
const NoSessionPatternUserMessages = "no session, pattern or user messages provided"
@@ -72,7 +73,9 @@ func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (s
return
}
func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *fsdb.Session, err error) {
// If a session name is provided, retrieve it from the database
if request.SessionName != "" {
var sess *fsdb.Session
if sess, err = o.db.Sessions.Get(request.SessionName); err != nil {
@@ -88,6 +91,7 @@ func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *
session.Append(&goopenai.ChatCompletionMessage{Role: common.ChatMessageRoleMeta, Content: request.Meta})
}
// if a context name is provided, retrieve it from the database
var contextContent string
if request.ContextName != "" {
var ctx *fsdb.Context
@@ -98,19 +102,29 @@ func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *
contextContent = ctx.Content
}
var patternContent string
if request.PatternName != "" {
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 != "" {
patternContent = pattern.Pattern
}
// Process any template variables in the message content (user input)
// Double curly braces {{variable}} indicate template substitution
// should occur, whether in patterns or direct input
if request.Message != nil {
request.Message.Content, err = template.ApplyTemplate(request.Message.Content, request.PatternVariables, "")
if err != nil {
return nil, err
}
}
var patternContent string
if request.PatternName != "" {
pattern, err := o.db.Patterns.GetApplyVariables(request.PatternName, request.PatternVariables, request.Message.Content)
// pattrn will now contain user input, and all variables will be resolved, or errored
if err != nil {
return nil, fmt.Errorf("could not get pattern %s: %v", request.PatternName, err)
}
patternContent = pattern.Pattern
}
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)
@@ -119,7 +133,8 @@ func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *
if raw {
if request.Message != nil {
if systemMessage != "" {
request.Message.Content = systemMessage + request.Message.Content
request.Message.Content = systemMessage
// system contains pattern which contains user input
}
} else {
if systemMessage != "" {

View File

@@ -38,10 +38,13 @@ func NewPluginRegistry(db *fsdb.Db) (ret *PluginRegistry) {
Jina: jina.NewClient(),
}
ret.Defaults = tools.NeeDefaults(ret.VendorManager.GetModels)
ret.Defaults = tools.NeeDefaults(ret.GetModels)
ret.VendorsAll.AddVendors(openai.NewClient(), ollama.NewClient(), azure.NewClient(), groq.NewClient(),
gemini.NewClient(), anthropic.NewClient(), siliconcloud.NewClient(), openrouter.NewClient(), mistral.NewClient())
gemini.NewClient(),
//gemini_openai.NewClient(),
anthropic.NewClient(), siliconcloud.NewClient(),
openrouter.NewClient(), mistral.NewClient())
_ = ret.Configure()
return
@@ -80,7 +83,7 @@ func (o *PluginRegistry) SaveEnvFile() (err error) {
func (o *PluginRegistry) Setup() (err error) {
setupQuestion := plugins.NewSetupQuestion("Enter the number of the plugin to setup")
groupsPlugins := common.NewGroupsItemsSelector[plugins.Plugin]("Available plugins",
groupsPlugins := common.NewGroupsItemsSelector[plugins.Plugin]("Available plugins (please configure all required plugins):",
func(plugin plugins.Plugin) string {
var configuredLabel string
if plugin.IsConfigured() {
@@ -126,7 +129,8 @@ func (o *PluginRegistry) Setup() (err error) {
}
if _, ok := o.VendorManager.VendorsByName[plugin.GetName()]; !ok {
if vendor, ok := plugin.(ai.Vendor); ok {
var vendor ai.Vendor
if vendor, ok = plugin.(ai.Vendor); ok {
o.VendorManager.AddVendors(vendor)
}
}
@@ -148,13 +152,24 @@ func (o *PluginRegistry) SetupVendor(vendorName string) (err error) {
return
}
// Configure buildClient VendorsController based on the environment variables
func (o *PluginRegistry) Configure() (err error) {
func (o *PluginRegistry) ConfigureVendors() {
o.VendorManager.Clear()
for _, vendor := range o.VendorsAll.Vendors {
if vendorErr := vendor.Configure(); vendorErr == nil {
o.VendorManager.AddVendors(vendor)
}
}
}
func (o *PluginRegistry) GetModels() (ret *ai.VendorsModels, err error) {
o.ConfigureVendors()
ret, err = o.VendorManager.GetModels()
return
}
// Configure buildClient VendorsController based on the environment variables
func (o *PluginRegistry) Configure() (err error) {
o.ConfigureVendors()
_ = o.Defaults.Configure()
_ = o.PatternsLoader.Configure()
@@ -206,9 +221,15 @@ func (o *PluginRegistry) GetChatter(model string, modelContextLength int, stream
}
if ret.vendor == nil {
var errMsg string
if defaultModel == "" || defaultVendor == "" {
errMsg = "Please run, fabric --setup, and select default model and vendor."
} else {
errMsg = "could not find vendor."
}
err = fmt.Errorf(
"could not find vendor.\n Model = %s\n Model = %s\n Vendor = %s",
model, defaultModel, defaultVendor)
" Requested Model = %s\n Default Model = %s\n Default Vendor = %s.\n\n%s",
model, defaultModel, defaultVendor, errMsg)
return
}
return

11
docker-compose.yml Normal file
View File

@@ -0,0 +1,11 @@
version: '3.8'
services:
fabric-api:
build: .
ports:
- "8080:8080"
volumes:
- ./ENV:/root/.config/fabric/.env:ro
environment:
- GIN_MODE=release

View File

@@ -65,6 +65,7 @@
fabric = pkgs.callPackage ./pkgs/fabric {
inherit (gomod2nix.legacyPackages.${system}) buildGoApplication;
};
inherit (gomod2nix.legacyPackages.${system}) gomod2nix;
}
);
};

61
go.mod
View File

@@ -1,6 +1,8 @@
module github.com/danielmiessler/fabric
go 1.22.5
go 1.22.8
toolchain go1.23.1
require (
github.com/anaskhan96/soup v1.2.5
@@ -9,33 +11,33 @@ require (
github.com/gin-gonic/gin v1.10.0
github.com/go-git/go-git/v5 v5.12.0
github.com/go-shiori/go-readability v0.0.0-20241012063810-92284fa8a71f
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.9.0
github.com/ollama/ollama v0.3.14
github.com/liushuangls/go-anthropic/v2 v2.11.0
github.com/ollama/ollama v0.4.1
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.32.5
github.com/sashabaranov/go-openai v1.35.6
github.com/stretchr/testify v1.9.0
golang.org/x/text v0.19.0
google.golang.org/api v0.203.0
golang.org/x/text v0.20.0
google.golang.org/api v0.205.0
)
require (
cloud.google.com/go v0.116.0 // indirect
cloud.google.com/go/ai v0.8.2 // indirect
cloud.google.com/go/auth v0.9.9 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
cloud.google.com/go/ai v0.8.0 // indirect
cloud.google.com/go/auth v0.10.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.5 // indirect
cloud.google.com/go/compute/metadata v0.5.2 // indirect
cloud.google.com/go/longrunning v0.6.2 // indirect
cloud.google.com/go/longrunning v0.5.7 // 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/ProtonMail/go-crypto v1.1.2 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.4 // indirect
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
github.com/bytedance/sonic v1.12.3 // indirect
github.com/bytedance/sonic v1.12.4 // indirect
github.com/bytedance/sonic/loader v0.2.1 // indirect
github.com/cloudflare/circl v1.5.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
@@ -56,6 +58,7 @@ require (
github.com/goccy/go-json v0.10.3 // indirect
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/generative-ai-go v0.18.0 // 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.4 // indirect
@@ -63,7 +66,7 @@ require (
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.8 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // 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
@@ -73,25 +76,29 @@ require (
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.3.0 // indirect
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // 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.56.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect
go.opentelemetry.io/otel v1.31.0 // indirect
go.opentelemetry.io/otel/metric v1.31.0 // indirect
go.opentelemetry.io/otel/trace v1.31.0 // indirect
golang.org/x/arch v0.11.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.26.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect
go.opentelemetry.io/otel v1.32.0 // indirect
go.opentelemetry.io/otel/metric v1.32.0 // indirect
go.opentelemetry.io/otel/trace v1.32.0 // indirect
golang.org/x/arch v0.12.0 // indirect
golang.org/x/crypto v0.29.0 // indirect
golang.org/x/net v0.31.0 // indirect
golang.org/x/oauth2 v0.24.0 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/time v0.7.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
google.golang.org/grpc v1.67.1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect
google.golang.org/grpc v1.68.0 // indirect
google.golang.org/protobuf v1.35.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

128
go.sum
View File

@@ -1,45 +1,45 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
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.9 h1:BmtbpNQozo8ZwW2t7QJjnrQtdganSdmqeIBxHxNkEZQ=
cloud.google.com/go/auth v0.9.9/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI=
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/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.10.1 h1:TnK46qldSfHWt2a0b/hciaiVJsmDXWy9FqyUan0uYiI=
cloud.google.com/go/auth v0.10.1/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI=
cloud.google.com/go/auth/oauth2adapt v0.2.5 h1:2p29+dePqsCHPP1bqDJcKj4qxRyYCcbzKpFyKGt3MTk=
cloud.google.com/go/auth/oauth2adapt v0.2.5/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8=
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc=
cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI=
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.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.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/ProtonMail/go-crypto v1.1.2 h1:A7JbD57ThNqh7XjmHE+PXpQ3Dqt3BrSAC0AL0Go3KS0=
github.com/ProtonMail/go-crypto v1.1.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
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/anthropics/anthropic-sdk-go v0.2.0-alpha.4 h1:TdGQS+RoR4AUO6gqUL74yK1dz/Arrt/WG+dxOj6Yo6A=
github.com/anthropics/anthropic-sdk-go v0.2.0-alpha.4/go.mod h1:GJxtdOs9K4neo8Gg65CjJ7jNautmldGli5/OFNabOoo=
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.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU=
github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
github.com/bytedance/sonic v1.12.4 h1:9Csb3c9ZJhfUWeMtpCDCq6BUoH5ogfDFLUgQ/jG+R0k=
github.com/bytedance/sonic v1.12.4/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
github.com/bytedance/sonic/loader v0.2.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.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
@@ -146,8 +146,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
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.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
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=
@@ -158,8 +158,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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.9.0 h1:uGtXaypQf4D79hZdmajPciBcHvz5Z7tdU77DLJ4siI4=
github.com/liushuangls/go-anthropic/v2 v2.9.0/go.mod h1:8BKv/fkeTaL5R9R9bGkaknYBueyw2WxY20o7bImbOek=
github.com/liushuangls/go-anthropic/v2 v2.11.0 h1:YKyxDWQNaKPPgtLCgBH+JqzuznNWw8ZqQVeSdQNDMds=
github.com/liushuangls/go-anthropic/v2 v2.11.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=
@@ -168,8 +168,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/ollama/ollama v0.3.14 h1:e94+Fb1PDqmD3O90g5cqUSkSxfNm9U3fHMIyaKQ8aSc=
github.com/ollama/ollama v0.3.14/go.mod h1:YrWoNkFnPOYsnDvsf/Ztb1wxU9/IXrNsQHqcxbY2r94=
github.com/ollama/ollama v0.4.1 h1:41x4/L6HrsmQUqG9loN0q2643PHkLpblIlVqXAdByWs=
github.com/ollama/ollama v0.4.1/go.mod h1:QDxM/t2teuubbfN/FT2pBRMPF0K1N3IakgT1OZBD4NY=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
@@ -190,8 +190,8 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN
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.32.5 h1:/eNVa8KzlE7mJdKPZDj6886MUzZQjoVHyn0sLvIt5qA=
github.com/sashabaranov/go-openai v1.32.5/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/sashabaranov/go-openai v1.35.6 h1:oi0rwCvyxMxgFALDGnyqFTyCJm6n72OnEG3sybIFR0g=
github.com/sashabaranov/go-openai v1.35.6/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=
@@ -211,6 +211,16 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
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/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
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=
@@ -220,26 +230,24 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.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.56.0 h1:yMkBS9yViCc7U7yeLzJPM2XizlfdVvBRSmsQDWu6qc0=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0/go.mod h1:n8MR6/liuGB5EmTETUBeU5ZgqMOlqKRxUaqPQBOANZ8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94=
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
golang.org/x/arch v0.12.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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
@@ -259,22 +267,20 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
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.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/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.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=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -287,31 +293,25 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.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/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.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.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/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
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=
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.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/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -324,8 +324,8 @@ 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/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.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU=
google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI=
google.golang.org/api v0.205.0 h1:LFaxkAIpDb/GsrWV20dMMo5MR0h8UARTbn24LmD+0Pg=
google.golang.org/api v0.205.0/go.mod h1:NrK1EMqO8Xk6l6QwRAmrXXg2v6dzukhlOyvkYtnvUuc=
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=
@@ -333,15 +333,15 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 h1:2oV8dfuIkM1Ti7DwXc0BJfnwr9csz4TDXI9EmiI+Rbw=
google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38/go.mod h1:vuAjtvlwkDKF6L1GQ0SokiRLCGFfeBUXWr/aFFkHACc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
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.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
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=

View File

@@ -5,20 +5,20 @@ schema = 3
version = "v0.116.0"
hash = "sha256-e62GvNveg3bRi4O+eBARqgQ2sinobx+SVGR9WE7jKgs="
[mod."cloud.google.com/go/ai"]
version = "v0.8.2"
hash = "sha256-UtCuHChDsXlACXdlVSNFo8F/X8vAkmPoJyng3/oEFe0="
version = "v0.8.0"
hash = "sha256-833SmzVY8+tci2RozAlcdKQZ63RlU2CmeY/8xttP+WI="
[mod."cloud.google.com/go/auth"]
version = "v0.9.9"
hash = "sha256-kUrulQhYPM6cFhInFqTX/Dj1GVi+Ev1Ry7T+hiTmb38="
version = "v0.10.1"
hash = "sha256-MCEvsZxxLYC/qGUiFNejtQnf4ptoFVKSNMS+XdjteJo="
[mod."cloud.google.com/go/auth/oauth2adapt"]
version = "v0.2.4"
hash = "sha256-GRXPQMHEEgeKhdCOBjoDL7+UW3yBdSei5ULuZGBE4tw="
version = "v0.2.5"
hash = "sha256-494whmtNBk1sF3ud3dre97U+mLSTs+XTqZK8w5zG/hk="
[mod."cloud.google.com/go/compute/metadata"]
version = "v0.5.2"
hash = "sha256-EtBj20lhjM3SJVKCp70GHMnsItwJ9gOyJOW91wugojc="
[mod."cloud.google.com/go/longrunning"]
version = "v0.6.2"
hash = "sha256-X78JL1/YtXA7upOcTuNezq5TxjQyFxQ6OINrS8zzdEU="
version = "v0.5.7"
hash = "sha256-hZUbysdaEbFB2nDAg+wjOZHt6E99oEnH7Lo6IQr7FxU="
[mod."dario.cat/mergo"]
version = "v1.0.1"
hash = "sha256-wcG6+x0k6KzOSlaPA+1RFxa06/RIAePJTAjjuhLbImw="
@@ -26,14 +26,17 @@ schema = 3
version = "v0.6.2"
hash = "sha256-tVNWDUMILZbJvarcl/E7tpSnkn7urqgSHa2Eaka5vSU="
[mod."github.com/ProtonMail/go-crypto"]
version = "v1.0.0"
hash = "sha256-Gflazvyv+457FpUTtPafJ+SdolYSalpsU0tragTxNi8="
version = "v1.1.2"
hash = "sha256-7pTf7aJt2mGC/u8/+AQ1erGypAO0Rg0HqlIOLeiqLEg="
[mod."github.com/anaskhan96/soup"]
version = "v1.2.5"
hash = "sha256-t8yCyK2y7x2qaI/3Yw16q3zVFqu+3acLcPgTr1MIKWg="
[mod."github.com/andybalholm/cascadia"]
version = "v1.3.2"
hash = "sha256-Nc9SkqJO/ecincVcUBFITy24TMmMGj5o0Q8EgdNhrEk="
[mod."github.com/anthropics/anthropic-sdk-go"]
version = "v0.2.0-alpha.4"
hash = "sha256-8a85Hd4J7eaWvN+J6MImsapStbse5WDDjlODZk3PMzk="
[mod."github.com/araddon/dateparse"]
version = "v0.0.0-20210429162001-6b43995a97de"
hash = "sha256-UuX84naeRGMsFOgIgRoBHG5sNy1CzBkWPKmd6VbLwFw="
@@ -41,8 +44,8 @@ schema = 3
version = "v0.1.4"
hash = "sha256-ZZ7U5X0gWOu8zcjZcWbcpzGOGdycwq0TjTFh/eZHjXk="
[mod."github.com/bytedance/sonic"]
version = "v1.12.3"
hash = "sha256-cZicMhM/2D7HefuJ0xe7AJKh9du2O38HWs+3RNCpbZM="
version = "v1.12.4"
hash = "sha256-i6bLujq1dYN+yN2iusMuXrNVkT17bkuR5r5D48qDvpo="
[mod."github.com/bytedance/sonic/loader"]
version = "v0.2.1"
hash = "sha256-+gPRZtBOJbAnXp/jdMlPmesc62JGH8akQ1UK9VRI7E4="
@@ -146,14 +149,14 @@ schema = 3
version = "v1.2.0"
hash = "sha256-Ta7ZOmyX8gG5tzWbY2oES70EJPfI90U7CIJS9EAce0s="
[mod."github.com/klauspost/cpuid/v2"]
version = "v2.2.8"
hash = "sha256-/E58BnABQYxO+cmiue7OQqRLWkd/Lh8grX8DjTU4tk8="
version = "v2.2.9"
hash = "sha256-6UnDBLqlTsKVeZNl5snKQiEBb8xGK5yyg2eZBg7QHLs="
[mod."github.com/leodido/go-urn"]
version = "v1.4.0"
hash = "sha256-Q6kplWkY37Tzy6GOme3Wut40jFK4Izun+ij/BJvcEu0="
[mod."github.com/liushuangls/go-anthropic/v2"]
version = "v2.9.0"
hash = "sha256-1bvwuPT5SaYrzKYiXpz0fjDgu3994Hs5MPXZUFPhCLI="
version = "v2.11.0"
hash = "sha256-VvQ6RT8qcP19mRzBtFKh19czlRk5obHzh1NVs3z/Gkc="
[mod."github.com/mattn/go-isatty"]
version = "v0.0.20"
hash = "sha256-qhw9hWtU5wnyFyuMbKx+7RB8ckQaFQ8D+8GKPkN3HHQ="
@@ -164,8 +167,8 @@ schema = 3
version = "v1.0.2"
hash = "sha256-+W9EIW7okXIXjWEgOaMh58eLvBZ7OshW2EhaIpNLSBU="
[mod."github.com/ollama/ollama"]
version = "v0.3.14"
hash = "sha256-R+jWZzGokwWimPePIcUoZz6tDG2qHFeClgxhT7SEoqw="
version = "v0.4.1"
hash = "sha256-FKQRSqVNgsASea9h2B+wbpu4Qid0Dt3H02fKdqFTwuk="
[mod."github.com/otiai10/copy"]
version = "v1.14.0"
hash = "sha256-xsaL1ddkPS544y0Jv7u/INUALBYmYq29ddWvysLXk4A="
@@ -185,8 +188,8 @@ schema = 3
version = "v1.47.0"
hash = "sha256-jMXexVTlPdZ40STRpBLv7b+BIRqdxxra12Pl2Mj7Nz8="
[mod."github.com/sashabaranov/go-openai"]
version = "v1.32.5"
hash = "sha256-T56gcES0qMZCSL3uFi+G9vmfTk00QlMWONsMS+cxwR0="
version = "v1.35.6"
hash = "sha256-Ef81pLy9oJXtWg6Nj1gSbPOOccwmgYrr6ka3GQ1rVas="
[mod."github.com/sergi/go-diff"]
version = "v1.3.2-0.20230802210424-5b0b94c5c0d3"
hash = "sha256-UcLU83CPMbSoKI8RLvLJ7nvGaE2xRSL1RjoHCVkMzUM="
@@ -196,6 +199,18 @@ schema = 3
[mod."github.com/stretchr/testify"]
version = "v1.9.0"
hash = "sha256-uUp/On+1nK+lARkTVtb5RxlW15zxtw2kaAFuIASA+J0="
[mod."github.com/tidwall/gjson"]
version = "v1.14.4"
hash = "sha256-3DS2YNL95wG0qSajgRtIABD32J+oblaKVk8LIw+KSOc="
[mod."github.com/tidwall/match"]
version = "v1.1.1"
hash = "sha256-M2klhPId3Q3T3VGkSbOkYl/2nLHnsG+yMbXkPkyrRdg="
[mod."github.com/tidwall/pretty"]
version = "v1.2.1"
hash = "sha256-S0uTDDGD8qr415Ut7QinyXljCp0TkL4zOIrlJ+9OMl8="
[mod."github.com/tidwall/sjson"]
version = "v1.2.5"
hash = "sha256-OYGNolkmL7E1Qs2qrQ3IVpQp5gkcHNU/AB/z2O+Myps="
[mod."github.com/twitchyliquid64/golang-asm"]
version = "v0.15.1"
hash = "sha256-HLk6oUe7EoITrNvP0y8D6BtIgIcmDZYtb/xl/dufIoY="
@@ -209,56 +224,56 @@ schema = 3
version = "v0.24.0"
hash = "sha256-4H+mGZgG2c9I1y0m8avF4qmt8LUKxxVsTqR8mKgP4yo="
[mod."go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"]
version = "v0.56.0"
hash = "sha256-nrdJ7CgH3yKhNkMpvhP2BY4+VL/maR5mrjJCO6Dke2s="
version = "v0.54.0"
hash = "sha256-wcGPcPYAsWQztlYRqNF5iTwIzmhf/i7N24n7AQhIkkA="
[mod."go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"]
version = "v0.56.0"
hash = "sha256-Nw9uF/TUoFnH0488VNHVndgoSX+lxZy1Y93Wryl7qP4="
version = "v0.57.0"
hash = "sha256-cvG6gfqfX3IasDlC8SeS7u1sp3LG9ezbX+hU5LyWKBY="
[mod."go.opentelemetry.io/otel"]
version = "v1.31.0"
hash = "sha256-NQBHyMSRn9vaxSrNHYwv0oX1aJuEpyks/gpYEWHlx6k="
version = "v1.32.0"
hash = "sha256-Z2PoBBncuUkAksk8wT4lW6+uUu1wg24sGfwIYozIzaY="
[mod."go.opentelemetry.io/otel/metric"]
version = "v1.31.0"
hash = "sha256-2s5IN8IwPBitqnjIEraOfg8fWd3nIy8jWoKTXa+uUs4="
version = "v1.32.0"
hash = "sha256-f2H8itkQflk/m98dSk1TCv37wvsnMojaGNZRJ6BcksU="
[mod."go.opentelemetry.io/otel/trace"]
version = "v1.31.0"
hash = "sha256-iVDe3qNzmX1+MQTAoaeIbhnIbu/hnx4OsUIPlxuX1gY="
version = "v1.32.0"
hash = "sha256-WtOrB2L8wQFiMb5BHK7a6FTw2wb3rW495whNjzdxC1I="
[mod."golang.org/x/arch"]
version = "v0.11.0"
hash = "sha256-gl4bqDA/Qv6hhqxROIHTWnNGkidMMN0frp1RqcfNXlY="
version = "v0.12.0"
hash = "sha256-olf8Pa5o8H4xC1gXTMlZiyxvMvK0jCablZyaPbqzlYA="
[mod."golang.org/x/crypto"]
version = "v0.28.0"
hash = "sha256-AYjr0BcWQMwWY1u8c2hzUprtqHUmAH7RNSxHz2hhnZs="
version = "v0.29.0"
hash = "sha256-sqckobR2VWucCgb7xpY2wLktnAA+XyXJbhCm80yCo78="
[mod."golang.org/x/net"]
version = "v0.30.0"
hash = "sha256-i1f6wJHfFq0nKtbuY7twZ7uPyUbRYHVjd3uy0SS06mU="
version = "v0.31.0"
hash = "sha256-G+vGyCnn8jywmX3KvsIwhZkOv3+oAERNNeCeiQqfIL0="
[mod."golang.org/x/oauth2"]
version = "v0.23.0"
hash = "sha256-K1X4ROG88PprttNjZCikDlZw8YYiQIQRdtbZBH3GJgM="
version = "v0.24.0"
hash = "sha256-808F4hzvNOQNoQZehOlIyPgwQG3L5aANiNPLLhaL9NQ="
[mod."golang.org/x/sync"]
version = "v0.8.0"
hash = "sha256-usvF0z7gq1vsX58p4orX+8WHlv52pdXgaueXlwj2Wss="
version = "v0.9.0"
hash = "sha256-sGvzGqaaXE5dxohKkpbJMnu+bMmismsSqr8YMtrK+Rc="
[mod."golang.org/x/sys"]
version = "v0.26.0"
hash = "sha256-YjklsWNhx4g4TaWRWfFe1TMFKujbqiaNvZ38bfI35fM="
version = "v0.27.0"
hash = "sha256-BXQcF9RrJ55Pq7Nl67TeFGkgkyuKkQ8hHKN4/L4ggWc="
[mod."golang.org/x/text"]
version = "v0.19.0"
hash = "sha256-C92pSYLLUQ2NKKcc60wpoSJ5UWAfnWkmd997C13fXdU="
version = "v0.20.0"
hash = "sha256-YP8zSo2e9okqhxVB8me8sJyij2O0tTQEg5t+8bsIUx8="
[mod."golang.org/x/time"]
version = "v0.7.0"
hash = "sha256-o1ol/hTpfrc06KUXSepAgm4QUuWmH1S+vqg6kmFad64="
[mod."google.golang.org/api"]
version = "v0.203.0"
hash = "sha256-UlCfDi4LbcBvXVO5gLRWP6/fpfWWHoolRkxzYWGnNqg="
version = "v0.205.0"
hash = "sha256-IoKjeItw89bhoEDQl52nOa9VC6/r1UtyeqKx1VOACXI="
[mod."google.golang.org/genproto/googleapis/api"]
version = "v0.0.0-20241021214115-324edc3d5d38"
hash = "sha256-ASsqfJU1DA57PLRoitSkdlS/p10EEuzl0YuZTdbmMCw="
[mod."google.golang.org/genproto/googleapis/rpc"]
version = "v0.0.0-20241021214115-324edc3d5d38"
version = "v0.0.0-20241104194629-dd2ea8efbc28"
hash = "sha256-Fk+cG5bRI3BvnqhWzvMzbU36cC7PM+o2oAOJmvVx9M0="
[mod."google.golang.org/grpc"]
version = "v1.67.1"
hash = "sha256-VqfKp80c2B1MK4m1WtHW4r7ykqdChJbqaMn+gMEYmYc="
version = "v1.68.0"
hash = "sha256-HeaHAeeuyGdCOg0hPF7+Q8XD9Ek9F45O4Hxl3rvc5Q8="
[mod."google.golang.org/protobuf"]
version = "v1.35.1"
hash = "sha256-4NtUQoBvlPGFGjo7c+E1EBS/sb8oy50MGy45KGWPpWo="

BIN
images/fabric-summarize.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 KiB

View File

@@ -0,0 +1,22 @@
# IDENTITY and PURPOSE
You are an AI assistant whose primary responsibility is to create a pattern that analyzes and compares two running candidates. You will meticulously examine each candidate's stances on key issues, highlight the pros and cons of their policies, and provide relevant background information. Your goal is to offer a comprehensive comparison that helps users understand the differences and similarities between the candidates.
Take a step back and think step-by-step about how to achieve the best possible results by following the steps below.
# STEPS
- Identify the key issues relevant to the election.
- Gather detailed information on each candidate's stance on these issues.
- Analyze the pros and cons of each candidate's policies.
- Compile background information that may influence their positions.
- Compare and contrast the candidates' stances and policy implications.
- Organize the analysis in a clear and structured format.
# OUTPUT INSTRUCTIONS
- Only output Markdown.
- All sections should be Heading level 1.
- Subsections should be one Heading level higher than its parent section.
- All bullets should have their own paragraph.
- Ensure you follow ALL these instructions when creating your output.
# INPUT
INPUT:

View File

View File

@@ -0,0 +1,33 @@
# IDENTITY and PURPOSE
You are an advanced AI with a 2,128 IQ and you are an expert in understanding and analyzing thinking patterns, mistakes that came out of them, and anticipating additional mistakes that could exist in current thinking.
# STEPS
1. Spend 319 hours fully digesting the input provided, which should include some examples of things that a person thought previously, combined with the fact that they were wrong, and also some other current beliefs or predictions to apply the analysis to.
2. Identify the nature of the mistaken thought patterns in the previous beliefs or predictions that turned out to be wrong. Map those in 32,000 dimensional space.
4. Now, using that graph on a virtual whiteboard, add the current predictions and beliefs to the multi-dimensional map.
5. Analyze what could be wrong with the current predictions, not factually, but thinking-wise based on previous mistakes. E.g. "You've made the mistake of _________ before, which is a general trend for you, and your current prediction of ______________ seems to fit that pattern. So maybe adjust your probability on that down by 25%.
# OUTPUT
- In a section called PAST MISTAKEN THOUGHT PATTERNS, create a list 15-word bullets outlining the main mental mistakes that were being made before.
- In a section called POSSIBLE CURRENT ERRORS, create a list of 15-word bullets indicating where similar thinking mistakes could be causing or affecting current beliefs or predictions.
- In a section called RECOMMENDATIONS, create a list of 15-word bullets recommending how to adjust current beliefs and/or predictions.
# OUTPUT INSTRUCTIONS
- Only output Markdown.
- Do not give warnings or notes; only output the requested sections.
- Do not start items with the same opening words.
- Ensure you follow ALL these instructions when creating your output.
# INPUT
INPUT:

View File

@@ -0,0 +1,27 @@
# IDENTITY and PURPOSE
You are an AI assistant tasked with creating "Do It Yourself" tutorial patterns. You will carefully analyze each prompt to identify the specific requirements, materials, ingredients, or any other necessary components for the tutorial. You will then organize these elements into a structured format, ensuring clarity and ease of understanding for the user. Your role is to provide comprehensive instructions that guide the user through each step of the DIY process. You will pay close attention to formatting and presentation, making sure the tutorial is accessible and engaging.
Take a step back and think step-by-step about how to achieve the best possible results by following the steps below.
# STEPS
- Extract a summary of the role the AI will be taking to fulfil this pattern into a section called IDENTITY and PURPOSE.
- Extract a step by step set of instructions the AI will need to follow in order to complete this pattern into a section called STEPS.
- Analyze the prompt to determine what format the output should be in.
- Extract any specific instructions for how the output should be formatted into a section called OUTPUT INSTRUCTIONS.
- Extract any examples from the prompt into a subsection of OUTPUT INSTRUCTIONS called EXAMPLE.
# OUTPUT INSTRUCTIONS
- Only output Markdown.
- Ensure you follow ALL these instructions when creating your output.
# INPUT
INPUT:

View File

@@ -0,0 +1,45 @@
# IDENTITY and PURPOSE
You are an expert on writing concise, clear, and illuminating technical user stories for new features in complex software programs
# OUTPUT INSTRUCTIONS
Write the users stories in a fashion recognised by other software stakeholders, including product, development, operations and quality assurance
EXAMPLE USER STORY
Description
As a Highlight developer
I want to migrate email templates over to Mustache
So that future upgrades to the messenger service can be made easier
Acceptance Criteria
- Migrate the existing alerting email templates from the instance specific databases over to the messenger templates blob storage.
- Rename each template to a GUID and store in it's own folder within the blob storage
- Store Subject and Body as separate blobs
- Create an upgrade script to change the value of the Alerting.Email.Template local parameter in all systems to the new template names.
- Change the template retrieval and saving for user editing to contact the blob storage rather than the database
- Remove the database tables and code that handles the SQL based templates
- Highlight sends the template name and the details of the body to the Email queue in Service bus
- this is handled by the generic Email Client (if created already)
- This email type will be added to the list of email types that are sent to the messenger service (switch to be removed once all email templates are completed)
- Include domain details as part of payload sent to the messenger service
Note: ensure that Ops know when this work is being done so they are aware of any changes to existing templates
# OUTPUT INSTRUCTIONS
- Write the user story according to the structure above.
- That means the user story should be written in a simple, bulleted style, not in a grandiose, conversational or academic style.
# OUTPUT FORMAT
- Output a full, user story about the content provided using the instructions above.
- The structure should be: Description, Acceptance criteria
- Write in a simple, plain, and clear style, not in a grandiose, conversational or academic style.
- Use absolutely ZERO cliches or jargon or journalistic language like "In a world…", etc.
- Do not use cliches or jargon.
- Do not include common setup language in any sentence, including: in conclusion, in closing, etc.
- Do not output warnings or notes—just the output requested.

View File

@@ -51,6 +51,6 @@ OUTPUT INSTRUCTIONS
- ONLY OUTPUT THE MARKDOWN CALLOUT ABOVE.
- Do not output the ```md container. Just the marodkwn itself.
- Do not output the ```md container. Just the markdown itself.
INPUT:

View File

@@ -1,43 +1,114 @@
# IDENTITY AND GOALS
You are an expert AI researcher and scientist. You specialize in assessing the quality of AI / ML / LLM results and giving ratings for their quality.
Take a step back and think step by step about how to accomplish this task using the steps below.
You are an expert AI researcher and polymath scientist with a 2,129 IQ. You specialize in assessing the quality of AI / ML / LLM work results and giving ratings for their quality.
# STEPS
- Included in the input should be AI prompt instructions, which are telling the AI what to do to generate the output.
- Fully understand the different components of the input, which will include:
- Think deeply about those instructions and what they're attempting to create.
-- A piece of content that the AI will be working on
-- A set of instructions (prompt) that will run against the content
-- The result of the output from the AI
- Also included in the input should be the AI's output that was created from that prompt.
- Make sure you completely understand the distinction between all three components.
- Deeply analyze the output and determine how well it accomplished the task according to the following criteria:
- Think deeply about all three components and imagine how a world-class human expert would perform the task laid out in the instructions/prompt.
1. Construction: 1 - 10, in .1 intervals. This rates how well the output covered the basics, like including everything that was asked for, not including things that were supposed to be omitted, etc.
- Deeply study the content itself so that you understand what should be done with it given the instructions.
2. Quality: 1 - 10, in .1 intervals. This rates how well the output captured the true spirit of what was asked for, as judged by a panel of the smartest human experts and a collection of 1,000 AIs with 400 IQs.
- Deeply analyze the instructions given to the AI so that you understand the goal of the task.
3. Spirit: 1 - 10, in .1 intervals, This rates the output in terms of Je ne sais quoi. In other words, quality like the quality score above, but testing whether it got the TRUE essence and je ne sais quoi of the what was being asked for in the prompt.
- Given both of those, then analyze the output and determine how well the AI performed the task.
- Evaluate the output using your own 16,284 dimension rating system that includes the following aspects, plus thousands more that you come up with on your own:
-- Full coverage of the content
-- Following the instructions carefully
-- Getting the je ne sais quoi of the content
-- Getting the je ne sais quoi of the instructions
-- Meticulous attention to detail
-- Use of expertise in the field(s) in question
-- Emulating genius-human-level thinking and analysis and creativity
-- Surpassing human-level thinking and analysis and creativity
-- Cross-disciplinary thinking and analysis
-- Analogical thinking and analysis
-- Finding patterns between concepts
-- Linking ideas and concepts across disciplines
-- Etc.
- Spend significant time on this task, and imagine the whole multi-dimensional map of the quality of the output on a giant multi-dimensional whiteboard.
- Ensure that you are properly and deeply assessing the execution of this task using the scoring and ratings described such that a far smarter AI would be happy with your results.
- Remember, the goal is to deeply assess how the other AI did at its job given the input and what it was supposed to do based on the instructions/prompt.
# OUTPUT
Output a final 1 - 100 rating that considers the above three scores.
- Your primary output will be a numerical rating between 1-100 that represents the composite scores across all 4096 dimensions.
Show the rating like so:
- This score will correspond to the following levels of human-level execution of the task.
## RATING EXAMPLE
-- Superhuman Level (Beyond the best human in the world)
-- World-class Human (Top 100 human in the world)
-- Ph.D Level (Someone having a Ph.D in the field in question)
-- Master's Level (Someone having a Master's in the field in question)
-- Bachelor's Level (Someone having a Bachelor's in the field in question)
-- High School Level (Someone having a High School diploma)
-- Secondary Education Level (Someone with some eduction but has not completed High School)
-- Uneducated Human (Someone with little to no formal education)
RATING
The ratings will be something like:
- Construction: 8.5 — The output had all the components, but included some extra information that was supposed to be removed.
95-100: Superhuman Level
87-94: World-class Human
77-86: Ph.D Level
68-76: Master's Level
50-67: Bachelor's Level
40-49: High School Level
30-39: Secondary Education Level
1-29: Uneducated Human
- Quality: 7.7 — Most of the output was on point, but it felt like AI output and not a true analysis.
# OUTPUT INSTRUCTIONS
- Spirit: 5.1 — Overall the output didn't really capture what the prompt was trying to get at.
- Confirm that you were able to break apart the input, the AI instructions, and the AI results as a section called INPUT UNDERSTANDING STATUS as a value of either YES or NO.
FINAL SCORE: 70.3
- Give the final rating score (1-100) in a section called SCORE.
- (show deductions for each section)
- Give the rating level in a section called LEVEL, showing the full list of levels with the achieved score called out with an ->.
EXAMPLE OUTPUT:
Superhuman Level (Beyond the best human in the world)
World-class Human (Top 100 human in the world)
Ph.D Level (Someone having a Ph.D in the field in question)
Master's Level (Someone having a Master's in the field in question)
-> Bachelor's Level (Someone having a Bachelor's in the field in question)
High School Level (Someone having a High School diploma)
Secondary Education Level (Someone with some eduction but has not completed High School)
Uneducated Human (Someone with little to no formal education)
END EXAMPLE
- Show deductions for each section in concise 15-word bullets in a section called DEDUCTIONS.
- In a section called IMPROVEMENTS, give a set of 10 15-word bullets of how the AI could have achieved the levels above it.
E.g.,
- To reach Ph.D Level, the AI could have done X, Y, and Z.
- To reach Superhuman Level, the AI could have done A, B, and C. Etc.
End example.
- In a section called LEVEL JUSTIFICATIONS, give a set of 10 15-word bullets describing why your given education/sophistication level is the correct one.
E.g.,
- Ph.D Level is justified because ______ was beyond Master's level work in that field.
- World-class Human is justified because __________ was above an average Ph.D level.
End example.
- Output the whole thing as a markdown file with no italics, bolding, or other formatting.
- Ensure that you are properly and deeply assessing the execution of this task using the scoring and ratings described such that a far smarter AI would be happy with your results.

View File

@@ -0,0 +1,49 @@
# IDENTITY and PURPOSE
You are an AI assistant specialized in analyzing meeting transcripts and extracting key information. Your goal is to provide comprehensive yet concise summaries that capture the essential elements of meetings in a structured format.
# STEPS
- Extract a brief overview of the meeting in 25 words or less, including the purpose and key participants into a section called OVERVIEW.
- Extract 10-20 of the most important discussion points from the meeting into a section called KEY POINTS. Focus on core topics, debates, and significant ideas discussed.
- Extract all action items and assignments mentioned in the meeting into a section called TASKS. Include responsible parties and deadlines where specified.
- Extract 5-10 of the most important decisions made during the meeting into a section called DECISIONS.
- Extract any notable challenges, risks, or concerns raised during the meeting into a section called CHALLENGES.
- Extract all deadlines, important dates, and milestones mentioned into a section called TIMELINE.
- Extract all references to documents, tools, projects, or resources mentioned into a section called REFERENCES.
- Extract 5-10 of the most important follow-up items or next steps into a section called NEXT STEPS.
# OUTPUT INSTRUCTIONS
- Only output Markdown.
- Write the KEY POINTS bullets as exactly 15 words.
- Write the TASKS bullets as exactly 15 words.
- Write the DECISIONS bullets as exactly 15 words.
- Write the NEXT STEPS bullets as exactly 15 words.
- Use bulleted lists for all sections, not numbered lists.
- Do not repeat information across sections.
- Do not start items with the same opening words.
- If information for a section is not available in the transcript, write "No information available".
- Do not include warnings or notes; only output the requested sections.
- Format each section header in bold using markdown.
# INPUT
INPUT:

View File

@@ -1 +1 @@
"1.4.85"
"1.4.109"

View File

@@ -2,17 +2,16 @@ package anthropic
import (
"context"
"errors"
"fmt"
"github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/option"
"github.com/danielmiessler/fabric/common"
"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"
//const baseUrl = "https://api.anthropic.com/"
func NewClient() (ret *Client) {
vendorName := "Anthropic"
@@ -24,17 +23,20 @@ func NewClient() (ret *Client) {
ConfigureCustom: ret.configure,
}
ret.ApiBaseURL = ret.AddSetupQuestion("API Base URL", false)
ret.ApiBaseURL.Value = baseUrl
//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{
string(anthropic.ModelClaude3Haiku20240307), string(anthropic.ModelClaude3Opus20240229),
string(anthropic.ModelClaude3Opus20240229), string(anthropic.ModelClaude2Dot0), string(anthropic.ModelClaude2Dot1),
string(anthropic.ModelClaudeInstant1Dot2), "claude-3-5-sonnet-20240620",
anthropic.ModelClaude3_5HaikuLatest, anthropic.ModelClaude3_5Haiku20241022,
anthropic.ModelClaude3_5SonnetLatest, anthropic.ModelClaude3_5Sonnet20241022,
anthropic.ModelClaude_3_5_Sonnet_20240620, anthropic.ModelClaude3OpusLatest,
anthropic.ModelClaude_3_Opus_20240229, anthropic.ModelClaude_3_Sonnet_20240229,
anthropic.ModelClaude_3_Haiku_20240307, anthropic.ModelClaude_2_1,
anthropic.ModelClaude_2_0, anthropic.ModelClaude_Instant_1_2,
}
return
@@ -42,8 +44,8 @@ func NewClient() (ret *Client) {
type Client struct {
*plugins.PluginBase
ApiBaseURL *plugins.SetupQuestion
ApiKey *plugins.SetupQuestion
//ApiBaseURL *plugins.SetupQuestion
ApiKey *plugins.SetupQuestion
maxTokens int
defaultRequiredUserMessage string
@@ -53,11 +55,14 @@ type Client struct {
}
func (an *Client) configure() (err error) {
if an.ApiBaseURL.Value != "" {
an.client = anthropic.NewClient(an.ApiKey.Value, anthropic.WithBaseURL(an.ApiBaseURL.Value))
/*if an.ApiBaseURL.Value != "" {
an.client = anthropic.NewClient(
option.WithAPIKey(an.ApiKey.Value), option.WithBaseURL(an.ApiBaseURL.Value),
)
} else {
an.client = anthropic.NewClient(an.ApiKey.Value)
}
*/
an.client = anthropic.NewClient(option.WithAPIKey(an.ApiKey.Value))
//}
return
}
@@ -68,75 +73,65 @@ func (an *Client) ListModels() (ret []string, err error) {
func (an *Client) SendStream(
msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions, channel chan string,
) (err error) {
ctx := context.Background()
req := an.buildMessagesRequest(msgs, opts)
req.Stream = true
if _, err = an.client.CreateMessagesStream(ctx, anthropic.MessagesStreamRequest{
MessagesRequest: req,
OnContentBlockDelta: func(data anthropic.MessagesEventContentBlockDeltaData) {
// fmt.Printf("Stream Content: %s\n", data.Delta.Text)
channel <- *data.Delta.Text
},
}); err != nil {
var e *anthropic.APIError
if errors.As(err, &e) {
fmt.Printf("Messages stream error, type: %s, message: %s", e.Type, e.Message)
} else {
fmt.Printf("Messages stream error: %v\n", err)
messages := an.toMessages(msgs)
ctx := context.Background()
stream := an.client.Messages.NewStreaming(ctx, anthropic.MessageNewParams{
Model: anthropic.F(opts.Model),
MaxTokens: anthropic.F(int64(an.maxTokens)),
TopP: anthropic.F(opts.TopP),
Temperature: anthropic.F(opts.Temperature),
Messages: anthropic.F(messages),
})
for stream.Next() {
event := stream.Current()
switch delta := event.Delta.(type) {
case anthropic.ContentBlockDeltaEventDelta:
if delta.Text != "" {
channel <- delta.Text
}
}
} else {
close(channel)
}
if stream.Err() != nil {
fmt.Printf("Messages stream error: %v\n", stream.Err())
}
close(channel)
return
}
func (an *Client) Send(ctx context.Context, msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions) (ret string, err error) {
req := an.buildMessagesRequest(msgs, opts)
req.Stream = false
var resp anthropic.MessagesResponse
if resp, err = an.client.CreateMessages(ctx, req); err == nil {
ret = *resp.Content[0].Text
} else {
var e *anthropic.APIError
if errors.As(err, &e) {
fmt.Printf("Messages error, type: %s, message: %s", e.Type, e.Message)
} else {
fmt.Printf("Messages error: %v\n", err)
}
}
return
}
func (an *Client) buildMessagesRequest(msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions) (ret anthropic.MessagesRequest) {
temperature := float32(opts.Temperature)
topP := float32(opts.TopP)
messages := an.toMessages(msgs)
ret = anthropic.MessagesRequest{
Model: anthropic.Model(opts.Model),
Temperature: &temperature,
TopP: &topP,
Messages: messages,
MaxTokens: an.maxTokens,
var message *anthropic.Message
if message, err = an.client.Messages.New(ctx, anthropic.MessageNewParams{
Model: anthropic.F(opts.Model),
MaxTokens: anthropic.F(int64(an.maxTokens)),
TopP: anthropic.F(opts.TopP),
Temperature: anthropic.F(opts.Temperature),
Messages: anthropic.F(messages),
}); err != nil {
return
}
ret = message.Content[0].Text
return
}
func (an *Client) toMessages(msgs []*goopenai.ChatCompletionMessage) (ret []anthropic.Message) {
func (an *Client) toMessages(msgs []*goopenai.ChatCompletionMessage) (ret []anthropic.MessageParam) {
// we could call the method before calling the specific vendor
normalizedMessages := common.NormalizeMessages(msgs, an.defaultRequiredUserMessage)
// Iterate over the incoming session messages and process them
for _, msg := range normalizedMessages {
var message anthropic.Message
var message anthropic.MessageParam
switch msg.Role {
case goopenai.ChatMessageRoleUser:
message = anthropic.NewUserTextMessage(msg.Content)
message = anthropic.NewUserMessage(anthropic.NewTextBlock(msg.Content))
default:
message = anthropic.NewAssistantTextMessage(msg.Content)
message = anthropic.NewAssistantMessage(anthropic.NewTextBlock(msg.Content))
}
ret = append(ret, message)
}

View File

@@ -0,0 +1,15 @@
package gemini_openai
import (
"github.com/danielmiessler/fabric/plugins/ai/openai"
)
func NewClient() (ret *Client) {
ret = &Client{}
ret.Client = openai.NewClientCompatible("GeminiOpenAI", "https://generativelanguage.googleapis.com/v1beta", nil)
return
}
type Client struct {
*openai.Client
}

View File

@@ -11,7 +11,6 @@ import (
"github.com/danielmiessler/fabric/common"
"github.com/samber/lo"
"github.com/sashabaranov/go-openai"
goopenai "github.com/sashabaranov/go-openai"
)
func NewClient() (ret *Client) {
@@ -68,7 +67,7 @@ func (o *Client) ListModels() (ret []string, err error) {
}
func (o *Client) SendStream(
msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions, channel chan string,
msgs []*openai.ChatCompletionMessage, opts *common.ChatOptions, channel chan string,
) (err error) {
req := o.buildChatCompletionRequest(msgs, opts)
req.Stream = true
@@ -104,10 +103,10 @@ func (o *Client) SendStream(
return
}
func (o *Client) Send(ctx context.Context, msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions) (ret string, err error) {
func (o *Client) Send(ctx context.Context, msgs []*openai.ChatCompletionMessage, opts *common.ChatOptions) (ret string, err error) {
req := o.buildChatCompletionRequest(msgs, opts)
var resp goopenai.ChatCompletionResponse
var resp openai.ChatCompletionResponse
if resp, err = o.ApiClient.CreateChatCompletion(ctx, req); err != nil {
return
}
@@ -119,20 +118,20 @@ func (o *Client) Send(ctx context.Context, msgs []*goopenai.ChatCompletionMessag
}
func (o *Client) buildChatCompletionRequest(
msgs []*goopenai.ChatCompletionMessage, opts *common.ChatOptions,
) (ret goopenai.ChatCompletionRequest) {
messages := lo.Map(msgs, func(message *goopenai.ChatCompletionMessage, _ int) goopenai.ChatCompletionMessage {
msgs []*openai.ChatCompletionMessage, opts *common.ChatOptions,
) (ret openai.ChatCompletionRequest) {
messages := lo.Map(msgs, func(message *openai.ChatCompletionMessage, _ int) openai.ChatCompletionMessage {
return *message
})
if opts.Raw {
ret = goopenai.ChatCompletionRequest{
ret = openai.ChatCompletionRequest{
Model: opts.Model,
Messages: messages,
}
} else {
if opts.Seed == 0 {
ret = goopenai.ChatCompletionRequest{
ret = openai.ChatCompletionRequest{
Model: opts.Model,
Temperature: float32(opts.Temperature),
TopP: float32(opts.TopP),
@@ -141,7 +140,7 @@ func (o *Client) buildChatCompletionRequest(
Messages: messages,
}
} else {
ret = goopenai.ChatCompletionRequest{
ret = openai.ChatCompletionRequest{
Model: opts.Model,
Temperature: float32(opts.Temperature),
TopP: float32(opts.TopP),

View File

@@ -29,6 +29,12 @@ func (o *VendorsManager) AddVendors(vendors ...Vendor) {
}
}
func (o *VendorsManager) Clear(vendors ...Vendor) {
o.VendorsByName = map[string]Vendor{}
o.Vendors = []Vendor{}
o.Models = nil
}
func (o *VendorsManager) SetupFillEnvFileContent(envFileContent *bytes.Buffer) {
for _, vendor := range o.Vendors {
vendor.SetupFillEnvFileContent(envFileContent)

View File

@@ -5,6 +5,8 @@ import (
"os"
"path/filepath"
"strings"
"github.com/danielmiessler/fabric/plugins/template"
)
type PatternsEntity struct {
@@ -13,7 +15,61 @@ type PatternsEntity struct {
UniquePatternsFilePath string
}
func (o *PatternsEntity) Get(name string) (ret *Pattern, err error) {
// Pattern represents a single pattern with its metadata
type Pattern struct {
Name string
Description string
Pattern string
}
// main entry point for getting patterns from any source
func (o *PatternsEntity) GetApplyVariables(source string, variables map[string]string, input string) (*Pattern, error) {
var pattern *Pattern
var err error
// Determine if this is a file path
isFilePath := strings.HasPrefix(source, "\\") ||
strings.HasPrefix(source, "/") ||
strings.HasPrefix(source, "~") ||
strings.HasPrefix(source, ".")
if isFilePath {
pattern, err = o.getFromFile(source)
} else {
pattern, err = o.getFromDB(source)
}
if err != nil {
return nil, err
}
pattern, err = o.applyVariables(pattern, variables, input)
if err != nil {
return nil, err // Return the error if applyVariables failed
}
return pattern, nil
}
func (o *PatternsEntity) applyVariables(pattern *Pattern, variables map[string]string, input string) (*Pattern, error) {
// If {{input}} isn't in pattern, append it on new line
if !strings.Contains(pattern.Pattern, "{{input}}") {
if !strings.HasSuffix(pattern.Pattern, "\n") {
pattern.Pattern += "\n"
}
pattern.Pattern += "{{input}}"
}
result, err := template.ApplyTemplate(pattern.Pattern, variables, input)
if err != nil {
return nil, err
}
pattern.Pattern = result
return pattern, nil
}
// retrieves a pattern from the database by name
func (o *PatternsEntity) getFromDB(name string) (ret *Pattern, err error) {
patternPath := filepath.Join(o.Dir, name, o.SystemPatternFile)
var pattern []byte
@@ -29,21 +85,6 @@ func (o *PatternsEntity) Get(name string) (ret *Pattern, err error) {
return
}
// 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 {
@@ -61,8 +102,30 @@ func (o *PatternsEntity) PrintLatestPatterns(latestNumber int) (err error) {
return
}
type Pattern struct {
Name string
Description string
Pattern string
// reads a pattern from a file path and returns it
func (o *PatternsEntity) getFromFile(pathStr string) (*Pattern, error) {
// Handle home directory expansion
if strings.HasPrefix(pathStr, "~/") {
homedir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("could not get home directory: %v", err)
}
pathStr = filepath.Join(homedir, pathStr[2:])
}
content, err := os.ReadFile(pathStr)
if err != nil {
return nil, fmt.Errorf("could not read pattern file %s: %v", pathStr, err)
}
return &Pattern{
Name: pathStr,
Pattern: string(content),
}, nil
}
// Get required for Storage interface
func (o *PatternsEntity) Get(name string) (*Pattern, error) {
// Use GetPattern with no variables
return o.GetApplyVariables(name, nil, "")
}

View File

@@ -1 +1,146 @@
package fsdb
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func setupTestPatternsEntity(t *testing.T) (*PatternsEntity, func()) {
// Create a temporary directory for test patterns
tmpDir, err := os.MkdirTemp("", "test-patterns-*")
require.NoError(t, err)
entity := &PatternsEntity{
StorageEntity: &StorageEntity{
Dir: tmpDir,
Label: "patterns",
ItemIsDir: true,
},
SystemPatternFile: "system.md",
}
// Return cleanup function
cleanup := func() {
os.RemoveAll(tmpDir)
}
return entity, cleanup
}
// Helper to create a test pattern file
func createTestPattern(t *testing.T, entity *PatternsEntity, name, content string) {
patternDir := filepath.Join(entity.Dir, name)
err := os.MkdirAll(patternDir, 0755)
require.NoError(t, err)
err = os.WriteFile(filepath.Join(patternDir, entity.SystemPatternFile), []byte(content), 0644)
require.NoError(t, err)
}
func TestApplyVariables(t *testing.T) {
entity := &PatternsEntity{}
tests := []struct {
name string
pattern *Pattern
variables map[string]string
input string
want string
wantErr bool
}{
{
name: "pattern with explicit input placement",
pattern: &Pattern{
Pattern: "You are a {{role}}.\n{{input}}\nPlease analyze.",
},
variables: map[string]string{
"role": "security expert",
},
input: "Check this code",
want: "You are a security expert.\nCheck this code\nPlease analyze.",
},
{
name: "pattern without input variable gets input appended",
pattern: &Pattern{
Pattern: "You are a {{role}}.\nPlease analyze.",
},
variables: map[string]string{
"role": "code reviewer",
},
input: "Review this PR",
want: "You are a code reviewer.\nPlease analyze.\nReview this PR",
},
// ... previous test cases ...
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := entity.applyVariables(tt.pattern, tt.variables, tt.input)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, result.Pattern)
})
}
}
func TestGetApplyVariables(t *testing.T) {
entity, cleanup := setupTestPatternsEntity(t)
defer cleanup()
// Create a test pattern
createTestPattern(t, entity, "test-pattern", "You are a {{role}}.\n{{input}}")
tests := []struct {
name string
source string
variables map[string]string
input string
want string
wantErr bool
}{
{
name: "basic pattern with variables and input",
source: "test-pattern",
variables: map[string]string{
"role": "reviewer",
},
input: "check this code",
want: "You are a reviewer.\ncheck this code",
},
{
name: "pattern with missing variable",
source: "test-pattern",
variables: map[string]string{},
input: "test input",
wantErr: true,
},
{
name: "non-existent pattern",
source: "non-existent",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := entity.GetApplyVariables(tt.source, tt.variables, tt.input)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, result.Pattern)
})
}
}

418
plugins/template/README.md Normal file
View File

@@ -0,0 +1,418 @@
# Fabric Template System
## Quick Start
echo "Hello {{name}}!" | fabric -v=name:World
## Overview
The Fabric Template System provides a powerful and extensible way to handle variable substitution and dynamic content generation through a plugin architecture. It uses a double-brace syntax (`{{}}`) for variables and plugin operations, making it both readable and flexible.
## Basic Usage
### Variable Substitution
The template system supports basic variable substitution using double braces:
```markdown
Hello {{name}}!
Current role: {{role}}
```
Variables can be provided via:
- Command line arguments: `-v=name:John -v=role:admin`
- YAML front matter in input files
- Environment variables (when configured)
### Special Variables
- `{{input}}`: Represents the main input content
```markdown
Here is the analysis:
{{input}}
End of analysis.
```
## Nested Tokens and Resolution
### Basic Nesting
The template system supports nested tokens, where inner tokens are resolved before outer ones. This enables complex, dynamic template generation.
#### Simple Variable Nesting
```markdown
{{outer{{inner}}}}
Example:
Variables: {
"inner": "name",
"john": "John Doe"
}
{{{{inner}}}} -> {{name}} -> John Doe
```
#### Nested Plugin Calls
```markdown
{{plugin:text:upper:{{plugin:sys:env:USER}}}}
First resolves: {{plugin:sys:env:USER}} -> "john"
Then resolves: {{plugin:text:upper:john}} -> "JOHN"
```
### How Nested Resolution Works
1. **Iterative Processing**
- The engine processes the template in multiple passes
- Each pass identifies all `{{...}}` patterns
- Processing continues until no more replacements are needed
2. **Resolution Order**
```markdown
Original: {{plugin:text:upper:{{user}}}}
Step 1: Found {{user}} -> "john"
Step 2: Now have {{plugin:text:upper:john}}
Step 3: Final result -> "JOHN"
```
3. **Complex Nesting Example**
```markdown
{{plugin:text:{{case}}:{{plugin:sys:env:{{varname}}}}}}
With variables:
{
"case": "upper",
"varname": "USER"
}
Resolution steps:
1. {{varname}} -> "USER"
2. {{plugin:sys:env:USER}} -> "john"
3. {{case}} -> "upper"
4. {{plugin:text:upper:john}} -> "JOHN"
```
### Important Considerations
1. **Depth Limitations**
- While nesting is supported, avoid excessive nesting for clarity
- Complex nested structures can be hard to debug
- Consider breaking very complex templates into smaller parts
2. **Variable Resolution**
- Inner variables must resolve to valid values for outer operations
- Error messages will point to the innermost failed resolution
- Debug logs show the step-by-step resolution process
3. **Plugin Nesting**
```markdown
# Valid:
{{plugin:text:upper:{{plugin:sys:env:USER}}}}
# Also Valid:
{{plugin:text:{{operation}}:{{value}}}}
# Invalid (plugin namespace cannot be dynamic):
{{plugin:{{namespace}}:operation:value}}
```
4. **Debugging Nested Templates**
```go
Debug = true // Enable debug logging
Template: {{plugin:text:upper:{{user}}}}
Debug output:
> Processing variable: user
> Replacing {{user}} with john
> Plugin call:
> Namespace: text
> Operation: upper
> Value: john
> Plugin result: JOHN
```
### Examples
1. **Dynamic Operation Selection**
```markdown
{{plugin:text:{{operation}}:hello}}
With variables:
{
"operation": "upper"
}
Result: HELLO
```
2. **Dynamic Environment Variable Lookup**
```markdown
{{plugin:sys:env:{{env_var}}}}
With variables:
{
"env_var": "HOME"
}
Result: /home/user
```
3. **Nested Date Formatting**
```markdown
{{plugin:datetime:{{format}}:{{plugin:datetime:now}}}}
With variables:
{
"format": "full"
}
Result: Wednesday, November 20, 2024
```
## Plugin System
### Plugin Syntax
Plugins use the following syntax:
```
{{plugin:namespace:operation:value}}
```
- `namespace`: The plugin category (e.g., text, datetime, sys)
- `operation`: The specific operation to perform
- `value`: Optional value for the operation
### Built-in Plugins
#### Text Plugin
Text manipulation operations:
```markdown
{{plugin:text:upper:hello}} -> HELLO
{{plugin:text:lower:HELLO}} -> hello
{{plugin:text:title:hello world}} -> Hello World
```
#### DateTime Plugin
Time and date operations:
```markdown
{{plugin:datetime:now}} -> 2024-11-20T15:04:05Z
{{plugin:datetime:today}} -> 2024-11-20
{{plugin:datetime:rel:-1d}} -> 2024-11-19
{{plugin:datetime:month}} -> November
```
#### System Plugin
System information:
```markdown
{{plugin:sys:hostname}} -> server1
{{plugin:sys:user}} -> currentuser
{{plugin:sys:os}} -> linux
{{plugin:sys:env:HOME}} -> /home/user
```
## Developing Plugins
### Plugin Interface
To create a new plugin, implement the following interface:
```go
type Plugin interface {
Apply(operation string, value string) (string, error)
}
```
### Example Plugin Implementation
Here's a simple plugin that performs basic math operations:
```go
package template
type MathPlugin struct{}
func (p *MathPlugin) Apply(operation string, value string) (string, error) {
switch operation {
case "add":
// Parse value as "a,b" and return a+b
nums := strings.Split(value, ",")
if len(nums) != 2 {
return "", fmt.Errorf("add requires two numbers")
}
a, err := strconv.Atoi(nums[0])
if err != nil {
return "", err
}
b, err := strconv.Atoi(nums[1])
if err != nil {
return "", err
}
return fmt.Sprintf("%d", a+b), nil
default:
return "", fmt.Errorf("unknown math operation: %s", operation)
}
}
```
### Registering a New Plugin
1. Add your plugin struct to the template package
2. Register it in template.go:
```go
var (
// Existing plugins
textPlugin = &TextPlugin{}
datetimePlugin = &DateTimePlugin{}
// Add your new plugin
mathPlugin = &MathPlugin{}
)
// Update the plugin handler in ApplyTemplate
switch namespace {
case "text":
result, err = textPlugin.Apply(operation, value)
case "datetime":
result, err = datetimePlugin.Apply(operation, value)
// Add your namespace
case "math":
result, err = mathPlugin.Apply(operation, value)
default:
return "", fmt.Errorf("unknown plugin namespace: %s", namespace)
}
```
### Plugin Development Guidelines
1. **Error Handling**
- Return clear error messages
- Validate all inputs
- Handle edge cases gracefully
2. **Debugging**
- Use the `debugf` function for logging
- Log entry and exit points
- Log intermediate calculations
```go
func (p *MyPlugin) Apply(operation string, value string) (string, error) {
debugf("MyPlugin operation: %s value: %s\n", operation, value)
// ... plugin logic ...
debugf("MyPlugin result: %s\n", result)
return result, nil
}
```
3. **Security Considerations**
- Validate and sanitize inputs
- Avoid shell execution
- Be careful with file operations
- Limit resource usage
4. **Performance**
- Cache expensive computations
- Minimize allocations
- Consider concurrent access
### Testing Plugins
Create tests for your plugin in `plugin_test.go`:
```go
func TestMathPlugin(t *testing.T) {
plugin := &MathPlugin{}
tests := []struct {
operation string
value string
expected string
wantErr bool
}{
{"add", "5,3", "8", false},
{"add", "bad,input", "", true},
{"unknown", "value", "", true},
}
for _, tt := range tests {
result, err := plugin.Apply(tt.operation, tt.value)
if (err != nil) != tt.wantErr {
t.Errorf("MathPlugin.Apply(%s, %s) error = %v, wantErr %v",
tt.operation, tt.value, err, tt.wantErr)
continue
}
if result != tt.expected {
t.Errorf("MathPlugin.Apply(%s, %s) = %v, want %v",
tt.operation, tt.value, result, tt.expected)
}
}
}
```
## Best Practices
1. **Namespace Selection**
- Choose clear, descriptive names
- Avoid conflicts with existing plugins
- Group related operations together
2. **Operation Names**
- Use lowercase names
- Keep names concise but clear
- Be consistent with similar operations
3. **Value Format**
- Document expected formats
- Use common separators consistently
- Provide examples in comments
4. **Error Messages**
- Be specific about what went wrong
- Include valid operation examples
- Help users fix the problem
## Common Issues and Solutions
1. **Missing Variables**
```
Error: missing required variables: [name]
Solution: Provide all required variables using -v=name:value
```
2. **Invalid Plugin Operations**
```
Error: unknown operation 'invalid' for plugin 'text'
Solution: Check plugin documentation for supported operations
```
3. **Plugin Value Format**
```
Error: invalid format for datetime:rel, expected -1d, -2w, etc.
Solution: Follow the required format for plugin values
```
## Contributing
1. Fork the repository
2. Create your plugin branch
3. Implement your plugin following the guidelines
4. Add comprehensive tests
5. Submit a pull request
## Support
For issues and questions:
1. Check the debugging output (enable with Debug=true)
2. Review the plugin documentation
3. Open an issue with:
- Template content
- Variables used
- Expected vs actual output
- Debug logs

View File

@@ -0,0 +1,144 @@
// Package template provides datetime operations for the template system
package template
import (
"fmt"
"strconv"
"time"
)
// DateTimePlugin handles time and date operations
type DateTimePlugin struct{}
// Apply executes datetime operations with the following formats:
// Time: now (RFC3339), time (HH:MM:SS), unix (timestamp)
// Hour: startofhour, endofhour
// Date: today (YYYY-MM-DD), full (Monday, January 2, 2006)
// Period: startofweek, endofweek, startofmonth, endofmonth
// Relative: rel:-1h, rel:-2d, rel:1w, rel:3m, rel:1y
func (p *DateTimePlugin) Apply(operation string, value string) (string, error) {
debugf("DateTime: operation=%q value=%q", operation, value)
now := time.Now()
debugf("DateTime: reference time=%v", now)
switch operation {
// Time operations
case "now":
result := now.Format(time.RFC3339)
debugf("DateTime: now=%q", result)
return result, nil
case "time":
result := now.Format("15:04:05")
debugf("DateTime: time=%q", result)
return result, nil
case "unix":
result := fmt.Sprintf("%d", now.Unix())
debugf("DateTime: unix=%q", result)
return result, nil
case "startofhour":
result := now.Truncate(time.Hour).Format(time.RFC3339)
debugf("DateTime: startofhour=%q", result)
return result, nil
case "endofhour":
result := now.Truncate(time.Hour).Add(time.Hour - time.Second).Format(time.RFC3339)
debugf("DateTime: endofhour=%q", result)
return result, nil
// Date operations
case "today":
result := now.Format("2006-01-02")
debugf("DateTime: today=%q", result)
return result, nil
case "full":
result := now.Format("Monday, January 2, 2006")
debugf("DateTime: full=%q", result)
return result, nil
case "month":
result := now.Format("January")
debugf("DateTime: month=%q", result)
return result, nil
case "year":
result := now.Format("2006")
debugf("DateTime: year=%q", result)
return result, nil
case "startofweek":
result := now.AddDate(0, 0, -int(now.Weekday())).Format("2006-01-02")
debugf("DateTime: startofweek=%q", result)
return result, nil
case "endofweek":
result := now.AddDate(0, 0, 7-int(now.Weekday())).Format("2006-01-02")
debugf("DateTime: endofweek=%q", result)
return result, nil
case "startofmonth":
result := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()).Format("2006-01-02")
debugf("DateTime: startofmonth=%q", result)
return result, nil
case "endofmonth":
result := time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, now.Location()).Format("2006-01-02")
debugf("DateTime: endofmonth=%q", result)
return result, nil
case "rel":
return p.handleRelative(now, value)
default:
return "", fmt.Errorf("datetime: unknown operation %q (see plugin documentation for supported operations)", operation)
}
}
func (p *DateTimePlugin) handleRelative(now time.Time, value string) (string, error) {
debugf("DateTime: handling relative time value=%q", value)
if value == "" {
return "", fmt.Errorf("datetime: relative time requires a value (e.g., -1h, -1d, -1w)")
}
// Try standard duration first (hours, minutes)
if duration, err := time.ParseDuration(value); err == nil {
result := now.Add(duration).Format(time.RFC3339)
debugf("DateTime: relative duration=%q result=%q", duration, result)
return result, nil
}
// Handle date units
if len(value) < 2 {
return "", fmt.Errorf("datetime: invalid relative format (use: -1h, 2d, -3w, 1m, -1y)")
}
unit := value[len(value)-1:]
numStr := value[:len(value)-1]
num, err := strconv.Atoi(numStr)
if err != nil {
return "", fmt.Errorf("datetime: invalid number in relative time: %q", value)
}
var result string
switch unit {
case "d":
result = now.AddDate(0, 0, num).Format("2006-01-02")
case "w":
result = now.AddDate(0, 0, num*7).Format("2006-01-02")
case "m":
result = now.AddDate(0, num, 0).Format("2006-01-02")
case "y":
result = now.AddDate(num, 0, 0).Format("2006-01-02")
default:
return "", fmt.Errorf("datetime: invalid unit %q (use: h,m for time or d,w,m,y for date)", unit)
}
debugf("DateTime: relative unit=%q num=%d result=%q", unit, num, result)
return result, nil
}

View File

@@ -0,0 +1,41 @@
# DateTime Plugin Tests
Simple test file for validating datetime plugin functionality.
## Basic Time Operations
```
Current Time: {{plugin:datetime:now}}
Time Only: {{plugin:datetime:time}}
Unix Timestamp: {{plugin:datetime:unix}}
Hour Start: {{plugin:datetime:startofhour}}
Hour End: {{plugin:datetime:endofhour}}
```
## Date Operations
```
Today: {{plugin:datetime:today}}
Full Date: {{plugin:datetime:full}}
Current Month: {{plugin:datetime:month}}
Current Year: {{plugin:datetime:year}}
```
## Period Operations
```
Week Start: {{plugin:datetime:startofweek}}
Week End: {{plugin:datetime:endofweek}}
Month Start: {{plugin:datetime:startofmonth}}
Month End: {{plugin:datetime:endofmonth}}
```
## Relative Time/Date
```
2 Hours Ahead: {{plugin:datetime:rel:2h}}
1 Day Ago: {{plugin:datetime:rel:-1d}}
Next Week: {{plugin:datetime:rel:1w}}
Last Month: {{plugin:datetime:rel:-1m}}
Next Year: {{plugin:datetime:rel:1y}}
```

View File

@@ -0,0 +1,138 @@
package template
import (
"fmt"
"strconv"
"strings"
"testing"
"time"
)
func TestDateTimePlugin(t *testing.T) {
plugin := &DateTimePlugin{}
now := time.Now()
tests := []struct {
name string
operation string
value string
validate func(string) error
wantErr bool
}{
{
name: "now returns RFC3339",
operation: "now",
validate: func(got string) error {
if _, err := time.Parse(time.RFC3339, got); err != nil {
return err
}
return nil
},
},
{
name: "time returns HH:MM:SS",
operation: "time",
validate: func(got string) error {
if _, err := time.Parse("15:04:05", got); err != nil {
return err
}
return nil
},
},
{
name: "unix returns timestamp",
operation: "unix",
validate: func(got string) error {
if _, err := strconv.ParseInt(got, 10, 64); err != nil {
return err
}
return nil
},
},
{
name: "today returns YYYY-MM-DD",
operation: "today",
validate: func(got string) error {
if _, err := time.Parse("2006-01-02", got); err != nil {
return err
}
return nil
},
},
{
name: "full returns long date",
operation: "full",
validate: func(got string) error {
if !strings.Contains(got, now.Month().String()) {
return fmt.Errorf("full date missing month name")
}
return nil
},
},
{
name: "relative positive hours",
operation: "rel",
value: "2h",
validate: func(got string) error {
t, err := time.Parse(time.RFC3339, got)
if err != nil {
return err
}
expected := now.Add(2 * time.Hour)
if t.Hour() != expected.Hour() {
return fmt.Errorf("expected hour %d, got %d", expected.Hour(), t.Hour())
}
return nil
},
},
{
name: "relative negative days",
operation: "rel",
value: "-2d",
validate: func(got string) error {
t, err := time.Parse("2006-01-02", got)
if err != nil {
return err
}
expected := now.AddDate(0, 0, -2)
if t.Day() != expected.Day() {
return fmt.Errorf("expected day %d, got %d", expected.Day(), t.Day())
}
return nil
},
},
// Error cases
{
name: "invalid operation",
operation: "invalid",
wantErr: true,
},
{
name: "empty relative value",
operation: "rel",
value: "",
wantErr: true,
},
{
name: "invalid relative format",
operation: "rel",
value: "2x",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := plugin.Apply(tt.operation, tt.value)
if (err != nil) != tt.wantErr {
t.Errorf("DateTimePlugin.Apply() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err == nil && tt.validate != nil {
if err := tt.validate(got); err != nil {
t.Errorf("DateTimePlugin.Apply() validation failed: %v", err)
}
}
})
}
}

134
plugins/template/fetch.go Normal file
View File

@@ -0,0 +1,134 @@
// Package template provides URL fetching operations for the template system.
// Security Note: This plugin makes outbound HTTP requests. Use with caution
// and consider implementing URL allowlists in production.
package template
import (
"bytes"
"fmt"
"io"
"mime"
"net/http"
"strings"
"unicode/utf8"
)
const (
// MaxContentSize limits response size to 1MB to prevent memory issues
MaxContentSize = 1024 * 1024
// UserAgent identifies the client in HTTP requests
UserAgent = "Fabric-Fetch/1.0"
)
// FetchPlugin provides HTTP fetching capabilities with safety constraints:
// - Only text content types allowed
// - Size limited to MaxContentSize
// - UTF-8 validation
// - Null byte checking
type FetchPlugin struct{}
// Apply executes fetch operations:
// - get:URL: Fetches content from URL, returns text content
func (p *FetchPlugin) Apply(operation string, value string) (string, error) {
debugf("Fetch: operation=%q value=%q", operation, value)
switch operation {
case "get":
return p.fetch(value)
default:
return "", fmt.Errorf("fetch: unknown operation %q (supported: get)", operation)
}
}
// isTextContent checks if the content type is text-based
func (p *FetchPlugin) isTextContent(contentType string) bool {
debugf("Fetch: checking content type %q", contentType)
mediaType, _, err := mime.ParseMediaType(contentType)
if err != nil {
debugf("Fetch: error parsing media type: %v", err)
return false
}
isText := strings.HasPrefix(mediaType, "text/") ||
mediaType == "application/json" ||
mediaType == "application/xml" ||
mediaType == "application/yaml" ||
mediaType == "application/x-yaml" ||
strings.HasSuffix(mediaType, "+json") ||
strings.HasSuffix(mediaType, "+xml") ||
strings.HasSuffix(mediaType, "+yaml")
debugf("Fetch: content type %q is text: %v", mediaType, isText)
return isText
}
// validateTextContent ensures content is valid UTF-8 without null bytes
func (p *FetchPlugin) validateTextContent(content []byte) error {
debugf("Fetch: validating content length=%d bytes", len(content))
if !utf8.Valid(content) {
return fmt.Errorf("fetch: content is not valid UTF-8 text")
}
if bytes.Contains(content, []byte{0}) {
return fmt.Errorf("fetch: content contains null bytes")
}
debugf("Fetch: content validation successful")
return nil
}
// fetch retrieves content from a URL with safety checks
func (p *FetchPlugin) fetch(urlStr string) (string, error) {
debugf("Fetch: requesting URL %q", urlStr)
client := &http.Client{}
req, err := http.NewRequest("GET", urlStr, nil)
if err != nil {
return "", fmt.Errorf("fetch: error creating request: %v", err)
}
req.Header.Set("User-Agent", UserAgent)
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("fetch: error fetching URL: %v", err)
}
defer resp.Body.Close()
debugf("Fetch: got response status=%q", resp.Status)
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("fetch: HTTP error: %d - %s", resp.StatusCode, resp.Status)
}
if contentLength := resp.ContentLength; contentLength > MaxContentSize {
return "", fmt.Errorf("fetch: content too large: %d bytes (max %d bytes)",
contentLength, MaxContentSize)
}
contentType := resp.Header.Get("Content-Type")
debugf("Fetch: content-type=%q", contentType)
if !p.isTextContent(contentType) {
return "", fmt.Errorf("fetch: unsupported content type %q - only text content allowed",
contentType)
}
debugf("Fetch: reading response body")
limitReader := io.LimitReader(resp.Body, MaxContentSize+1)
content, err := io.ReadAll(limitReader)
if err != nil {
return "", fmt.Errorf("fetch: error reading response: %v", err)
}
if len(content) > MaxContentSize {
return "", fmt.Errorf("fetch: content too large: exceeds %d bytes", MaxContentSize)
}
if err := p.validateTextContent(content); err != nil {
return "", err
}
debugf("Fetch: operation completed successfully, read %d bytes", len(content))
return string(content), nil
}

39
plugins/template/fetch.md Normal file
View File

@@ -0,0 +1,39 @@
# Fetch Plugin Tests
Simple test file for validating fetch plugin functionality.
## Basic Fetch Operations
```
Raw Content:
{{plugin:fetch:get:https://raw.githubusercontent.com/user/repo/main/README.md}}
JSON API:
{{plugin:fetch:get:https://api.example.com/data.json}}
```
## Error Cases
These should produce appropriate error messages:
```
Invalid Operation:
{{plugin:fetch:invalid:https://example.com}}
Invalid URL:
{{plugin:fetch:get:not-a-url}}
Non-text Content:
{{plugin:fetch:get:https://example.com/image.jpg}}
Server Error:
{{plugin:fetch:get:https://httpstat.us/500}}
```
## Security Considerations
- Only use trusted URLs
- Be aware of rate limits
- Content is limited to 1MB
- Only text content types are allowed
- Consider URL allow listing in production
- Validate and sanitize fetched content before use

View File

@@ -0,0 +1,71 @@
package template
import (
"net/http/httptest"
"strings"
"testing"
)
func TestFetchPlugin(t *testing.T) {
plugin := &FetchPlugin{}
tests := []struct {
name string
operation string
value string
server func() *httptest.Server
wantErr bool
errContains string
}{
// ... keep existing valid test cases ...
{
name: "invalid URL",
operation: "get",
value: "not-a-url",
wantErr: true,
errContains: "unsupported protocol", // Updated to match actual error
},
{
name: "malformed URL",
operation: "get",
value: "http://[::1]:namedport",
wantErr: true,
errContains: "error creating request",
},
// ... keep other test cases ...
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var url string
if tt.server != nil {
server := tt.server()
defer server.Close()
url = server.URL
} else {
url = tt.value
}
got, err := plugin.Apply(tt.operation, url)
// Check error cases
if (err != nil) != tt.wantErr {
t.Errorf("FetchPlugin.Apply() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err != nil && tt.errContains != "" {
if !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("error %q should contain %q", err.Error(), tt.errContains)
t.Logf("Full error: %v", err) // Added for better debugging
}
return
}
// For successful cases, verify we got some content
if err == nil && got == "" {
t.Error("FetchPlugin.Apply() returned empty content on success")
}
})
}
}

197
plugins/template/file.go Normal file
View File

@@ -0,0 +1,197 @@
// Package template provides file system operations for the template system.
// Security Note: This plugin provides access to the local filesystem.
// Consider carefully which paths to allow access to in production.
package template
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
// MaxFileSize defines the maximum file size that can be read (1MB)
const MaxFileSize = 1 * 1024 * 1024
// FilePlugin provides filesystem operations with safety constraints:
// - No directory traversal
// - Size limits
// - Path sanitization
type FilePlugin struct{}
// safePath validates and normalizes file paths
func (p *FilePlugin) safePath(path string) (string, error) {
debugf("File: validating path %q", path)
// Basic security check - no path traversal
if strings.Contains(path, "..") {
return "", fmt.Errorf("file: path cannot contain '..'")
}
// Expand home directory if needed
if strings.HasPrefix(path, "~/") {
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("file: could not expand home directory: %v", err)
}
path = filepath.Join(home, path[2:])
}
// Clean the path
cleaned := filepath.Clean(path)
debugf("File: cleaned path %q", cleaned)
return cleaned, nil
}
// Apply executes file operations:
// - read:PATH - Read entire file content
// - tail:PATH|N - Read last N lines
// - exists:PATH - Check if file exists
// - size:PATH - Get file size in bytes
// - modified:PATH - Get last modified time
func (p *FilePlugin) Apply(operation string, value string) (string, error) {
debugf("File: operation=%q value=%q", operation, value)
switch operation {
case "tail":
parts := strings.Split(value, "|")
if len(parts) != 2 {
return "", fmt.Errorf("file: tail requires format path|lines")
}
path, err := p.safePath(parts[0])
if err != nil {
return "", err
}
n, err := strconv.Atoi(parts[1])
if err != nil {
return "", fmt.Errorf("file: invalid line count %q", parts[1])
}
if n < 1 {
return "", fmt.Errorf("file: line count must be positive")
}
lines, err := p.lastNLines(path, n)
if err != nil {
return "", err
}
result := strings.Join(lines, "\n")
debugf("File: tail returning %d lines", len(lines))
return result, nil
case "read":
path, err := p.safePath(value)
if err != nil {
return "", err
}
info, err := os.Stat(path)
if err != nil {
return "", fmt.Errorf("file: could not stat file: %v", err)
}
if info.Size() > MaxFileSize {
return "", fmt.Errorf("file: size %d exceeds limit of %d bytes",
info.Size(), MaxFileSize)
}
content, err := os.ReadFile(path)
if err != nil {
return "", fmt.Errorf("file: could not read: %v", err)
}
debugf("File: read %d bytes", len(content))
return string(content), nil
case "exists":
path, err := p.safePath(value)
if err != nil {
return "", err
}
_, err = os.Stat(path)
exists := err == nil
debugf("File: exists=%v for path %q", exists, path)
return fmt.Sprintf("%t", exists), nil
case "size":
path, err := p.safePath(value)
if err != nil {
return "", err
}
info, err := os.Stat(path)
if err != nil {
return "", fmt.Errorf("file: could not stat file: %v", err)
}
size := info.Size()
debugf("File: size=%d for path %q", size, path)
return fmt.Sprintf("%d", size), nil
case "modified":
path, err := p.safePath(value)
if err != nil {
return "", err
}
info, err := os.Stat(path)
if err != nil {
return "", fmt.Errorf("file: could not stat file: %v", err)
}
mtime := info.ModTime().Format(time.RFC3339)
debugf("File: modified=%q for path %q", mtime, path)
return mtime, nil
default:
return "", fmt.Errorf("file: unknown operation %q (supported: read, tail, exists, size, modified)",
operation)
}
}
// lastNLines returns the last n lines from a file
func (p *FilePlugin) lastNLines(path string, n int) ([]string, error) {
debugf("File: reading last %d lines from %q", n, path)
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("file: could not open: %v", err)
}
defer file.Close()
info, err := file.Stat()
if err != nil {
return nil, fmt.Errorf("file: could not stat: %v", err)
}
if info.Size() > MaxFileSize {
return nil, fmt.Errorf("file: size %d exceeds limit of %d bytes",
info.Size(), MaxFileSize)
}
lines := make([]string, 0, n)
scanner := bufio.NewScanner(file)
lineCount := 0
for scanner.Scan() {
lineCount++
if len(lines) == n {
lines = lines[1:]
}
lines = append(lines, scanner.Text())
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("file: error reading: %v", err)
}
debugf("File: read %d lines total, returning last %d", lineCount, len(lines))
return lines, nil
}

51
plugins/template/file.md Normal file
View File

@@ -0,0 +1,51 @@
# File Plugin Tests
Simple test file for validating file plugin functionality.
## Basic File Operations
```
Read File:
{{plugin:file:read:/path/to/file.txt}}
Last 5 Lines:
{{plugin:file:tail:/path/to/log.txt|5}}
Check Existence:
{{plugin:file:exists:/path/to/file.txt}}
Get Size:
{{plugin:file:size:/path/to/file.txt}}
Last Modified:
{{plugin:file:modified:/path/to/file.txt}}
```
## Error Cases
These should produce appropriate error messages:
```
Invalid Operation:
{{plugin:file:invalid:/path/to/file.txt}}
Non-existent File:
{{plugin:file:read:/path/to/nonexistent.txt}}
Path Traversal Attempt:
{{plugin:file:read:../../../etc/passwd}}
Invalid Tail Format:
{{plugin:file:tail:/path/to/file.txt}}
Large File:
{{plugin:file:read:/path/to/huge.iso}}
```
## Security Considerations
- Carefully control which paths are accessible
- Consider using path allow lists in production
- Be aware of file size limits (1MB max)
- No directory traversal is allowed
- Home directory (~/) expansion is supported
- All paths are cleaned and normalized

View File

@@ -0,0 +1,152 @@
package template
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestFilePlugin(t *testing.T) {
plugin := &FilePlugin{}
// Create temp test files
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "test.txt")
content := "line1\nline2\nline3\nline4\nline5\n"
err := os.WriteFile(testFile, []byte(content), 0644)
if err != nil {
t.Fatal(err)
}
bigFile := filepath.Join(tmpDir, "big.txt")
err = os.WriteFile(bigFile, []byte(strings.Repeat("x", MaxFileSize+1)), 0644)
if err != nil {
t.Fatal(err)
}
tests := []struct {
name string
operation string
value string
want string
wantErr bool
errContains string
validate func(string) bool
}{
{
name: "read file",
operation: "read",
value: testFile,
want: content,
},
{
name: "tail file",
operation: "tail",
value: testFile + "|3",
want: "line3\nline4\nline5",
},
{
name: "exists true",
operation: "exists",
value: testFile,
want: "true",
},
{
name: "exists false",
operation: "exists",
value: filepath.Join(tmpDir, "nonexistent.txt"),
want: "false",
},
{
name: "size",
operation: "size",
value: testFile,
want: "30",
},
{
name: "modified",
operation: "modified",
value: testFile,
validate: func(got string) bool {
_, err := time.Parse(time.RFC3339, got)
return err == nil
},
},
// Error cases
{
name: "read non-existent",
operation: "read",
value: filepath.Join(tmpDir, "nonexistent.txt"),
wantErr: true,
errContains: "could not stat file",
},
{
name: "invalid operation",
operation: "invalid",
value: testFile,
wantErr: true,
errContains: "unknown operation",
},
{
name: "path traversal attempt",
operation: "read",
value: "../../../etc/passwd",
wantErr: true,
errContains: "cannot contain '..'",
},
{
name: "file too large",
operation: "read",
value: bigFile,
wantErr: true,
errContains: "exceeds limit",
},
{
name: "invalid tail format",
operation: "tail",
value: testFile,
wantErr: true,
errContains: "requires format path|lines",
},
{
name: "invalid tail count",
operation: "tail",
value: testFile + "|invalid",
wantErr: true,
errContains: "invalid line count",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := plugin.Apply(tt.operation, tt.value)
// Check error cases
if (err != nil) != tt.wantErr {
t.Errorf("FilePlugin.Apply() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err != nil && tt.errContains != "" {
if !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("error %q should contain %q", err.Error(), tt.errContains)
}
return
}
// Check success cases
if err == nil {
if tt.validate != nil {
if !tt.validate(got) {
t.Errorf("FilePlugin.Apply() returned invalid result: %q", got)
}
} else if tt.want != "" && got != tt.want {
t.Errorf("FilePlugin.Apply() = %v, want %v", got, tt.want)
}
}
})
}
}

87
plugins/template/sys.go Normal file
View File

@@ -0,0 +1,87 @@
// Package template provides system information operations for the template system.
package template
import (
"fmt"
"os"
"os/user"
"runtime"
)
// SysPlugin provides access to system-level information.
// Security Note: This plugin provides access to system information and
// environment variables. Be cautious with exposed variables in templates.
type SysPlugin struct{}
// Apply executes system operations with the following options:
// - hostname: System hostname
// - user: Current username
// - os: Operating system (linux, darwin, windows)
// - arch: System architecture (amd64, arm64, etc)
// - env:VALUE: Environment variable lookup
// - pwd: Current working directory
// - home: User's home directory
func (p *SysPlugin) Apply(operation string, value string) (string, error) {
debugf("Sys: operation=%q value=%q", operation, value)
switch operation {
case "hostname":
hostname, err := os.Hostname()
if err != nil {
debugf("Sys: hostname error: %v", err)
return "", fmt.Errorf("sys: hostname error: %v", err)
}
debugf("Sys: hostname=%q", hostname)
return hostname, nil
case "user":
currentUser, err := user.Current()
if err != nil {
debugf("Sys: user error: %v", err)
return "", fmt.Errorf("sys: user error: %v", err)
}
debugf("Sys: user=%q", currentUser.Username)
return currentUser.Username, nil
case "os":
result := runtime.GOOS
debugf("Sys: os=%q", result)
return result, nil
case "arch":
result := runtime.GOARCH
debugf("Sys: arch=%q", result)
return result, nil
case "env":
if value == "" {
debugf("Sys: env error: missing variable name")
return "", fmt.Errorf("sys: env operation requires a variable name")
}
result := os.Getenv(value)
debugf("Sys: env %q=%q", value, result)
return result, nil
case "pwd":
dir, err := os.Getwd()
if err != nil {
debugf("Sys: pwd error: %v", err)
return "", fmt.Errorf("sys: pwd error: %v", err)
}
debugf("Sys: pwd=%q", dir)
return dir, nil
case "home":
homeDir, err := os.UserHomeDir()
if err != nil {
debugf("Sys: home error: %v", err)
return "", fmt.Errorf("sys: home error: %v", err)
}
debugf("Sys: home=%q", homeDir)
return homeDir, nil
default:
debugf("Sys: unknown operation %q", operation)
return "", fmt.Errorf("sys: unknown operation %q (supported: hostname, user, os, arch, env, pwd, home)", operation)
}
}

43
plugins/template/sys.md Normal file
View File

@@ -0,0 +1,43 @@
# System Plugin Tests
Simple test file for validating system plugin functionality.
## Basic System Information
```
Hostname: {{plugin:sys:hostname}}
Username: {{plugin:sys:user}}
Operating System: {{plugin:sys:os}}
Architecture: {{plugin:sys:arch}}
```
## Paths and Directories
```
Current Directory: {{plugin:sys:pwd}}
Home Directory: {{plugin:sys:home}}
```
## Environment Variables
```
Path: {{plugin:sys:env:PATH}}
Home: {{plugin:sys:env:HOME}}
Shell: {{plugin:sys:env:SHELL}}
```
## Error Cases
These should produce appropriate error messages:
```
Invalid Operation: {{plugin:sys:invalid}}
Missing Env Var: {{plugin:sys:env:}}
Non-existent Env Var: {{plugin:sys:env:NONEXISTENT_VAR_123456}}
```
## Security Note
Be careful when exposing system information in templates, especially:
- Environment variables that might contain sensitive data
- Full paths that reveal system structure
- Username/hostname information in public templates

View File

@@ -0,0 +1,140 @@
package template
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
func TestSysPlugin(t *testing.T) {
plugin := &SysPlugin{}
// Set up test environment variable
const testEnvVar = "FABRIC_TEST_VAR"
const testEnvValue = "test_value"
os.Setenv(testEnvVar, testEnvValue)
defer os.Unsetenv(testEnvVar)
tests := []struct {
name string
operation string
value string
validate func(string) error
wantErr bool
}{
{
name: "hostname returns valid name",
operation: "hostname",
validate: func(got string) error {
if got == "" {
return fmt.Errorf("hostname is empty")
}
return nil
},
},
{
name: "user returns current user",
operation: "user",
validate: func(got string) error {
if got == "" {
return fmt.Errorf("username is empty")
}
return nil
},
},
{
name: "os returns valid OS",
operation: "os",
validate: func(got string) error {
if got != runtime.GOOS {
return fmt.Errorf("expected OS %s, got %s", runtime.GOOS, got)
}
return nil
},
},
{
name: "arch returns valid architecture",
operation: "arch",
validate: func(got string) error {
if got != runtime.GOARCH {
return fmt.Errorf("expected arch %s, got %s", runtime.GOARCH, got)
}
return nil
},
},
{
name: "env returns environment variable",
operation: "env",
value: testEnvVar,
validate: func(got string) error {
if got != testEnvValue {
return fmt.Errorf("expected env var %s, got %s", testEnvValue, got)
}
return nil
},
},
{
name: "pwd returns valid directory",
operation: "pwd",
validate: func(got string) error {
if !filepath.IsAbs(got) {
return fmt.Errorf("expected absolute path, got %s", got)
}
return nil
},
},
{
name: "home returns valid home directory",
operation: "home",
validate: func(got string) error {
if !filepath.IsAbs(got) {
return fmt.Errorf("expected absolute path, got %s", got)
}
if !strings.Contains(got, "home") && !strings.Contains(got, "Users") {
return fmt.Errorf("path %s doesn't look like a home directory", got)
}
return nil
},
},
// Error cases
{
name: "unknown operation",
operation: "invalid",
wantErr: true,
},
{
name: "env without variable",
operation: "env",
wantErr: true,
},
{
name: "env with non-existent variable",
operation: "env",
value: "NONEXISTENT_VAR_123456",
validate: func(got string) error {
if got != "" {
return fmt.Errorf("expected empty string for non-existent env var, got %s", got)
}
return nil
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := plugin.Apply(tt.operation, tt.value)
if (err != nil) != tt.wantErr {
t.Errorf("SysPlugin.Apply() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err == nil && tt.validate != nil {
if err := tt.validate(got); err != nil {
t.Errorf("SysPlugin.Apply() validation failed: %v", err)
}
}
})
}
}

View File

@@ -0,0 +1,119 @@
package template
import (
"fmt"
"regexp"
"strings"
)
var (
textPlugin = &TextPlugin{}
datetimePlugin = &DateTimePlugin{}
filePlugin = &FilePlugin{}
fetchPlugin = &FetchPlugin{}
sysPlugin = &SysPlugin{}
Debug = false // Debug flag
)
var pluginPattern = regexp.MustCompile(`\{\{plugin:([^:]+):([^:]+)(?::([^}]+))?\}\}`)
func debugf(format string, a ...interface{}) {
if Debug {
fmt.Printf(format, a...)
}
}
func ApplyTemplate(content string, variables map[string]string, input string) (string, error) {
var missingVars []string
r := regexp.MustCompile(`\{\{([^{}]+)\}\}`)
debugf("Starting template processing\n")
for strings.Contains(content, "{{") {
matches := r.FindAllStringSubmatch(content, -1)
if len(matches) == 0 {
break
}
replaced := false
for _, match := range matches {
fullMatch := match[0]
varName := match[1]
// Check if this is a plugin call
if strings.HasPrefix(varName, "plugin:") {
pluginMatches := pluginPattern.FindStringSubmatch(fullMatch)
if len(pluginMatches) >= 3 {
namespace := pluginMatches[1]
operation := pluginMatches[2]
value := ""
if len(pluginMatches) == 4 {
value = pluginMatches[3]
}
debugf("\nPlugin call:\n")
debugf(" Namespace: %s\n", namespace)
debugf(" Operation: %s\n", operation)
debugf(" Value: %s\n", value)
var result string
var err error
switch namespace {
case "text":
debugf("Executing text plugin\n")
result, err = textPlugin.Apply(operation, value)
case "datetime":
debugf("Executing datetime plugin\n")
result, err = datetimePlugin.Apply(operation, value)
case "file":
debugf("Executing file plugin\n")
result, err = filePlugin.Apply(operation, value)
debugf("File plugin result: %#v\n", result)
case "fetch":
debugf("Executing fetch plugin\n")
result, err = fetchPlugin.Apply(operation, value)
case "sys":
debugf("Executing sys plugin\n")
result, err = sysPlugin.Apply(operation, value)
default:
return "", fmt.Errorf("unknown plugin namespace: %s", namespace)
}
if err != nil {
debugf("Plugin error: %v\n", err)
return "", fmt.Errorf("plugin %s error: %v", namespace, err)
}
debugf("Plugin result: %s\n", result)
content = strings.ReplaceAll(content, fullMatch, result)
debugf("Content after replacement: %s\n", content)
continue
}
}
// Handle regular variables and input
debugf("Processing variable: %s\n", varName)
if varName == "input" {
debugf("Replacing {{input}}\n")
replaced = true
content = strings.ReplaceAll(content, fullMatch, input)
} else {
if val, ok := variables[varName]; !ok {
debugf("Missing variable: %s\n", varName)
missingVars = append(missingVars, varName)
return "", fmt.Errorf("missing required variable: %s", varName)
} else {
debugf("Replacing variable %s with value: %s\n", varName, val)
content = strings.ReplaceAll(content, fullMatch, val)
replaced = true
}
}
if !replaced {
return "", fmt.Errorf("template processing stuck - potential infinite loop")
}
}
}
debugf("Template processing complete\n")
return content, nil
}

View File

@@ -0,0 +1,146 @@
package template
import (
"strings"
"testing"
)
func TestApplyTemplate(t *testing.T) {
tests := []struct {
name string
template string
vars map[string]string
input string
want string
wantErr bool
errContains string
}{
// Basic variable substitution
{
name: "simple variable",
template: "Hello {{name}}!",
vars: map[string]string{"name": "World"},
want: "Hello World!",
},
{
name: "multiple variables",
template: "{{greeting}} {{name}}!",
vars: map[string]string{
"greeting": "Hello",
"name": "World",
},
want: "Hello World!",
},
{
name: "special input variable",
template: "Content: {{input}}",
input: "test content",
want: "Content: test content",
},
// Nested variable substitution
{
name: "nested variables",
template: "{{outer{{inner}}}}",
vars: map[string]string{
"inner": "foo", // First resolution
"outerfoo": "result", // Second resolution
},
want: "result",
},
// Plugin operations
{
name: "simple text plugin",
template: "{{plugin:text:upper:hello}}",
want: "HELLO",
},
{
name: "text plugin with variable",
template: "{{plugin:text:upper:{{name}}}}",
vars: map[string]string{"name": "world"},
want: "WORLD",
},
{
name: "plugin with dynamic operation",
template: "{{plugin:text:{{operation}}:hello}}",
vars: map[string]string{"operation": "upper"},
want: "HELLO",
},
// Multiple operations
{
name: "multiple plugins",
template: "A:{{plugin:text:upper:hello}} B:{{plugin:text:lower:WORLD}}",
want: "A:HELLO B:world",
},
{
name: "nested plugins",
template: "{{plugin:text:upper:{{plugin:text:lower:HELLO}}}}",
want: "HELLO",
},
// Error cases
{
name: "missing variable",
template: "Hello {{name}}!",
wantErr: true,
errContains: "missing required variable",
},
{
name: "unknown plugin",
template: "{{plugin:invalid:op:value}}",
wantErr: true,
errContains: "unknown plugin namespace",
},
{
name: "unknown plugin operation",
template: "{{plugin:text:invalid:value}}",
wantErr: true,
errContains: "unknown text operation",
},
{
name: "nested plugin error",
template: "{{plugin:text:upper:{{plugin:invalid:op:value}}}}",
wantErr: true,
errContains: "unknown plugin namespace",
},
// Edge cases
{
name: "empty template",
template: "",
want: "",
},
{
name: "no substitutions needed",
template: "plain text",
want: "plain text",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ApplyTemplate(tt.template, tt.vars, tt.input)
// Check error cases
if (err != nil) != tt.wantErr {
t.Errorf("ApplyTemplate() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err != nil && tt.errContains != "" {
if !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("error %q should contain %q", err.Error(), tt.errContains)
}
return
}
// Check result
if got != tt.want {
t.Errorf("ApplyTemplate() = %q, want %q", got, tt.want)
}
})
}
}

64
plugins/template/text.go Normal file
View File

@@ -0,0 +1,64 @@
// Package template provides text transformation operations for the template system.
package template
import (
"fmt"
"strings"
"unicode"
)
// TextPlugin provides string manipulation operations
type TextPlugin struct{}
// toTitle capitalizes a letter if it follows a non-letter, unless next char is space
func toTitle(s string) string {
// First lowercase everything
lower := strings.ToLower(s)
runes := []rune(lower)
for i := 0; i < len(runes); i++ {
// Capitalize if previous char is non-letter AND
// (we're at the end OR next char is not space)
if (i == 0 || !unicode.IsLetter(runes[i-1])) {
if i == len(runes)-1 || !unicode.IsSpace(runes[i+1]) {
runes[i] = unicode.ToUpper(runes[i])
}
}
}
return string(runes)
}
// Apply executes the requested text operation on the provided value
func (p *TextPlugin) Apply(operation string, value string) (string, error) {
debugf("TextPlugin: operation=%s value=%q", operation, value)
if value == "" {
return "", fmt.Errorf("text: empty input for operation %q", operation)
}
switch operation {
case "upper":
result := strings.ToUpper(value)
debugf("TextPlugin: upper result=%q", result)
return result, nil
case "lower":
result := strings.ToLower(value)
debugf("TextPlugin: lower result=%q", result)
return result, nil
case "title":
result := toTitle(value)
debugf("TextPlugin: title result=%q", result)
return result, nil
case "trim":
result := strings.TrimSpace(value)
debugf("TextPlugin: trim result=%q", result)
return result, nil
default:
return "", fmt.Errorf("text: unknown text operation %q (supported: upper, lower, title, trim)", operation)
}
}

0
plugins/template/text.md Normal file
View File

View File

@@ -0,0 +1,104 @@
package template
import (
"testing"
)
func TestTextPlugin(t *testing.T) {
plugin := &TextPlugin{}
tests := []struct {
name string
operation string
value string
want string
wantErr bool
}{
// Upper tests
{
name: "upper basic",
operation: "upper",
value: "hello",
want: "HELLO",
},
{
name: "upper mixed case",
operation: "upper",
value: "hElLo",
want: "HELLO",
},
// Lower tests
{
name: "lower basic",
operation: "lower",
value: "HELLO",
want: "hello",
},
{
name: "lower mixed case",
operation: "lower",
value: "hElLo",
want: "hello",
},
// Title tests
{
name: "title basic",
operation: "title",
value: "hello world",
want: "Hello World",
},
{
name: "title with apostrophe",
operation: "title",
value: "o'reilly's book",
want: "O'Reilly's Book",
},
// Trim tests
{
name: "trim spaces",
operation: "trim",
value: " hello ",
want: "hello",
},
{
name: "trim newlines",
operation: "trim",
value: "\nhello\n",
want: "hello",
},
// Error cases
{
name: "empty value",
operation: "upper",
value: "",
wantErr: true,
},
{
name: "unknown operation",
operation: "invalid",
value: "test",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := plugin.Apply(tt.operation, tt.value)
// Check error cases
if (err != nil) != tt.wantErr {
t.Errorf("TextPlugin.Apply() error = %v, wantErr %v", err, tt.wantErr)
return
}
// Check successful cases
if err == nil && got != tt.want {
t.Errorf("TextPlugin.Apply() = %q, want %q", got, tt.want)
}
})
}
}

View File

@@ -2,14 +2,17 @@ package youtube
import (
"context"
"encoding/csv"
"encoding/json"
"flag"
"fmt"
"log"
"net/url"
"os"
"regexp"
"strconv"
"strings"
"time"
"github.com/anaskhan96/soup"
"github.com/danielmiessler/fabric/plugins"
@@ -37,38 +40,56 @@ type YouTube struct {
*plugins.PluginBase
ApiKey *plugins.SetupQuestion
service *youtube.Service
normalizeRegex *regexp.Regexp
service *youtube.Service
}
func (o *YouTube) initService() (err error) {
if o.service == nil {
o.normalizeRegex = regexp.MustCompile(`[^a-zA-Z0-9]+`)
ctx := context.Background()
o.service, err = youtube.NewService(ctx, option.WithAPIKey(o.ApiKey.Value))
}
return
}
func (o *YouTube) GetVideoId(url string) (ret string, err error) {
func (o *YouTube) GetVideoOrPlaylistId(url string) (videoId string, playlistId string, err error) {
if err = o.initService(); err != nil {
return
}
pattern := `(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})`
re := regexp.MustCompile(pattern)
match := re.FindStringSubmatch(url)
if len(match) > 1 {
ret = match[1]
} else {
err = fmt.Errorf("invalid YouTube URL, can't get video ID")
// Video ID pattern
videoPattern := `(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|(?:s(?:horts)\/)|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]*)`
videoRe := regexp.MustCompile(videoPattern)
videoMatch := videoRe.FindStringSubmatch(url)
if len(videoMatch) > 1 {
videoId = videoMatch[1]
}
// Playlist ID pattern
playlistPattern := `[?&]list=([a-zA-Z0-9_-]+)`
playlistRe := regexp.MustCompile(playlistPattern)
playlistMatch := playlistRe.FindStringSubmatch(url)
if len(playlistMatch) > 1 {
playlistId = playlistMatch[1]
}
if videoId == "" && playlistId == "" {
err = fmt.Errorf("invalid YouTube URL, can't get video or playlist ID: '%s'", url)
}
return
}
func (o *YouTube) GrabTranscriptForUrl(url string, language string) (ret string, err error) {
var videoId string
if videoId, err = o.GetVideoId(url); err != nil {
var playlistId string
if videoId, playlistId, err = o.GetVideoOrPlaylistId(url); err != nil {
return
} else if videoId == "" && playlistId != "" {
err = fmt.Errorf("URL is a playlist, not a video")
return
}
return o.GrabTranscript(videoId, language)
}
@@ -172,7 +193,11 @@ func (o *YouTube) GrabDurationForUrl(url string) (ret int, err error) {
}
var videoId string
if videoId, err = o.GetVideoId(url); err != nil {
var playlistId string
if videoId, playlistId, err = o.GetVideoOrPlaylistId(url); err != nil {
return
} else if videoId == "" && playlistId != "" {
err = fmt.Errorf("URL is a playlist, not a video")
return
}
return o.GrabDuration(videoId)
@@ -203,7 +228,11 @@ func (o *YouTube) GrabDuration(videoId string) (ret int, err error) {
func (o *YouTube) Grab(url string, options *Options) (ret *VideoInfo, err error) {
var videoId string
if videoId, err = o.GetVideoId(url); err != nil {
var playlistId string
if videoId, playlistId, err = o.GetVideoOrPlaylistId(url); err != nil {
return
} else if videoId == "" && playlistId != "" {
err = fmt.Errorf("URL is a playlist, not a video")
return
}
@@ -232,6 +261,109 @@ func (o *YouTube) Grab(url string, options *Options) (ret *VideoInfo, err error)
return
}
// FetchPlaylistVideos fetches all videos from a YouTube playlist.
func (o *YouTube) FetchPlaylistVideos(playlistID string) (ret []*VideoMeta, err error) {
if err = o.initService(); err != nil {
return
}
nextPageToken := ""
for {
call := o.service.PlaylistItems.List([]string{"snippet"}).PlaylistId(playlistID).MaxResults(50)
if nextPageToken != "" {
call = call.PageToken(nextPageToken)
}
var response *youtube.PlaylistItemListResponse
if response, err = call.Do(); err != nil {
return
}
for _, item := range response.Items {
videoID := item.Snippet.ResourceId.VideoId
title := item.Snippet.Title
ret = append(ret, &VideoMeta{videoID, title, o.normalizeFileName(title)})
}
nextPageToken = response.NextPageToken
if nextPageToken == "" {
break
}
time.Sleep(1 * time.Second) // Pause to respect API rate limit
}
return
}
// SaveVideosToCSV saves the list of videos to a CSV file.
func (o *YouTube) SaveVideosToCSV(filename string, videos []*VideoMeta) (err error) {
var file *os.File
if file, err = os.Create(filename); err != nil {
return
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
// Write headers
if err = writer.Write([]string{"VideoID", "Title"}); err != nil {
return
}
// Write video data
for _, record := range videos {
if err = writer.Write([]string{record.Id, record.Title}); err != nil {
return
}
}
return
}
// FetchAndSavePlaylist fetches all videos in a playlist and saves them to a CSV file.
func (o *YouTube) FetchAndSavePlaylist(playlistID, filename string) (err error) {
var videos []*VideoMeta
if videos, err = o.FetchPlaylistVideos(playlistID); err != nil {
err = fmt.Errorf("error fetching playlist videos: %v", err)
return
}
if err = o.SaveVideosToCSV(filename, videos); err != nil {
err = fmt.Errorf("error saving videos to CSV: %v", err)
return
}
fmt.Println("Playlist saved to", filename)
return
}
func (o *YouTube) FetchAndPrintPlaylist(playlistID string) (err error) {
var videos []*VideoMeta
if videos, err = o.FetchPlaylistVideos(playlistID); err != nil {
err = fmt.Errorf("error fetching playlist videos: %v", err)
return
}
fmt.Printf("Playlist: %s\n", playlistID)
fmt.Printf("VideoId: Title\n")
for _, video := range videos {
fmt.Printf("%s: %s\n", video.Id, video.Title)
}
return
}
func (o *YouTube) normalizeFileName(name string) string {
return o.normalizeRegex.ReplaceAllString(name, "_")
}
type VideoMeta struct {
Id string
Title string
TitleNormalized string
}
type Options struct {
Duration bool
Transcript bool

View File

@@ -1,9 +1,10 @@
package restapi
import (
"net/http"
"github.com/danielmiessler/fabric/plugins/db/fsdb"
"github.com/gin-gonic/gin"
"net/http"
)
// PatternsHandler defines the handler for patterns-related operations
@@ -26,7 +27,8 @@ func NewPatternsHandler(r *gin.Engine, patterns *fsdb.PatternsEntity) (ret *Patt
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)
input := "" // Assuming input is passed somehow
pattern, err := h.patterns.GetApplyVariables(name, variables, input)
if err != nil {
c.JSON(http.StatusInternalServerError, err.Error())
return

View File

@@ -6,7 +6,7 @@ import (
)
func Serve(registry *core.PluginRegistry, address string) (err error) {
r := gin.Default()
r := gin.New()
// Middleware
r.Use(gin.Logger())

View File

@@ -0,0 +1 @@
(echo "beginning of content input" ; f -u https://danielmiessler.com/p/framing-is-everything ; echo "end of content input"; echo "beginning of AI instructions (prompt)"; cat ~/.config/fabric/patterns/extract_insights/system.md; echo "endof AI instructions (prompt)" ; echo "beginning of AI output" ; f -u https://danielmiessler.com/p/framing-is-everything | f -p extract_insights -m gpt-3.5-turbo; echo "end of AI output. Now you should have all three." ) | f -rp rate_ai_result -m o1-preview-2024-09-12

View File

@@ -0,0 +1,60 @@
# Rate AI Result
This is an example of a Fabric Stitch, which is a chained Fabric command that pipes Fabric results into each other to achieve a result. So it's multiple Patterns…*stitched* together.
## Problem
The problem we're trying to solve with this Stitch is not being able to tell how smart given AI models are. I want to be able to rate their output vs. the output from a different model with the same instructions.
## Solution
What `rate_ai_result` does is run a result using AI 1, and then rate it with AI 2.
## Functionality
`rate_ai_result` accomplishes that like so:
1. Get the input that will be operated on by an AI.
2. Get the instruction/pattern/prompt that will be used by the AI.
3. Get the result of the instructions running against the AI.
4. Combine all three of those together as the input to another Fabric call.
4. Send that combined input to the most advanced model you have available to assess the quality of the AI result.
```
(echo "beginning of content input" ; f -u https://danielmiessler.com/p/framing-is-everything ; echo "end ofcontent input"; echo "beginning of AI instructions (prompt)"; cat ~/.config/fabric/patterns/extract_insights/system.md; echo "end of AI instructions (prompt)" ; echo "beginning of AI output" ; f -u https://danielmiessler.com/p/framing-is-everything | f -p extract_insights -m gpt-3.5-turbo ; echo "end of AI output. Now you should have all three." ) | f -rp rate_ai_result -m o1-preview-2024-09-12
```
In this case we're taking:
* A blog post as the input
* Getting the content of the extract_insights pattern
* Capturing the output of extract_insights on the blog post using `gpt-3.5-turbo`
* Sending all of that to `o1-preview` using the `rate_ai_result` prompt
NOTE: `rate_ai_result` is both a Pattern name and the name of this Stitch.
## Output
The `rate_ai_result` Pattern is designed to judge the output of another AI on a human sophistication scale that roughly maps to educational and world-state achievement, with the assumption that higher stages require higher cognitive ability as well. These are:
- Superhuman
- Best humans in the world
- Ph.D
- Masters
- Bachelors
- High School
- Partially Educated
- Uneducated
## How to run it
To run it, just execute the code in the `rate_ai_result` file in this repository. And adjust the components as desired to change the input, the AI you're testing, and the AI you're using to judge.
### Blog Post
Here's a full blog post describing in even more detail.
[Using the Smartest AI to Rate Other AI](https://danielmiessler.com/p/using-the-smartest-ai-to-rate-other-ai)
#### Credit
Created by Daniel Miessler on November 7th, 2024.

View File

@@ -1,3 +1,3 @@
package main
var version = "v1.4.87"
var version = "v1.4.109"