mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-09 22:38:10 -05:00
Compare commits
132 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d8b46b878 | ||
|
|
fbd6083079 | ||
|
|
0320e17652 | ||
|
|
09fb913279 | ||
|
|
ec5ed689bb | ||
|
|
43ca0dccf7 | ||
|
|
fcfcf55610 | ||
|
|
188235efc5 | ||
|
|
fdd1d614b2 | ||
|
|
fc67dea243 | ||
|
|
efd363d5fb | ||
|
|
a7d6de1661 | ||
|
|
c0ade48648 | ||
|
|
7fd4fa4742 | ||
|
|
41b2e66c5c | ||
|
|
ed657383fb | ||
|
|
4d5d8d8b30 | ||
|
|
e9a75528ab | ||
|
|
c5ec4b548a | ||
|
|
8e87529638 | ||
|
|
ca33208fa1 | ||
|
|
3f8bca8728 | ||
|
|
ba56c33cf6 | ||
|
|
6ee4fdd366 | ||
|
|
30af189ae3 | ||
|
|
be998ff588 | ||
|
|
6bb3238e6d | ||
|
|
dfcd29593d | ||
|
|
63b357168e | ||
|
|
317a4309f7 | ||
|
|
eceb10b725 | ||
|
|
34f508fd82 | ||
|
|
9fa8634083 | ||
|
|
a3ea63c1f9 | ||
|
|
097b3eb0ba | ||
|
|
30f37ea633 | ||
|
|
23b495c8f7 | ||
|
|
e7f2d48437 | ||
|
|
7043f78f1f | ||
|
|
f2cc718f49 | ||
|
|
edb814c9f0 | ||
|
|
21de69b7d9 | ||
|
|
d4b5c3b8d5 | ||
|
|
afb5857699 | ||
|
|
153b8217fd | ||
|
|
beeba6989a | ||
|
|
666a1d32a3 | ||
|
|
4ed512b8d4 | ||
|
|
af16494be1 | ||
|
|
9afa397c27 | ||
|
|
58f9d3c89c | ||
|
|
7732b6fe55 | ||
|
|
0d5f15edda | ||
|
|
4e2aa1b6d8 | ||
|
|
b6eb969b3a | ||
|
|
4c22965f4b | ||
|
|
7d28c95f48 | ||
|
|
94b713e3a5 | ||
|
|
dccc92e8e0 | ||
|
|
590a9e452d | ||
|
|
56322aaeb5 | ||
|
|
3684031f44 | ||
|
|
005f2b7db5 | ||
|
|
67840605fc | ||
|
|
d475e7b568 | ||
|
|
1f07ea25a2 | ||
|
|
08f4e28342 | ||
|
|
97666d9537 | ||
|
|
f7733f932b | ||
|
|
20a039a8ab | ||
|
|
29856e4749 | ||
|
|
47a797e884 | ||
|
|
d4079aa543 | ||
|
|
62eb837422 | ||
|
|
8d81f8d3aa | ||
|
|
e8acf9ca07 | ||
|
|
af4752d324 | ||
|
|
fbd1fbfc67 | ||
|
|
d1fe826f14 | ||
|
|
b758a27b93 | ||
|
|
2ae26dc2a6 | ||
|
|
81d765a34c | ||
|
|
c396288ca7 | ||
|
|
125e7a341f | ||
|
|
064ab9ba85 | ||
|
|
f0ee8287a7 | ||
|
|
47ccc33dfc | ||
|
|
ceb735482a | ||
|
|
473a20c0f6 | ||
|
|
a337e81a81 | ||
|
|
7d773b51d0 | ||
|
|
bca10ddf7c | ||
|
|
9756c575f3 | ||
|
|
d02fb3e34d | ||
|
|
988ff88a15 | ||
|
|
5de85c3da5 | ||
|
|
5907f9dbac | ||
|
|
1293e37525 | ||
|
|
0a55e6c742 | ||
|
|
ff3b18485f | ||
|
|
2fec6e2e52 | ||
|
|
9250f19d15 | ||
|
|
1e7c5c3b6a | ||
|
|
0289b67a84 | ||
|
|
8934dbaa42 | ||
|
|
75c3d7ea6a | ||
|
|
a94ad620bc | ||
|
|
c6ca1a60d1 | ||
|
|
4321c9d518 | ||
|
|
6cd86639ce | ||
|
|
3e6ad1029c | ||
|
|
1cf967582d | ||
|
|
5e1b4e87e7 | ||
|
|
76d6788231 | ||
|
|
73a0e38af6 | ||
|
|
ff0ee4f111 | ||
|
|
de61e56fda | ||
|
|
79b03c681a | ||
|
|
b8de34e539 | ||
|
|
6377f951d8 | ||
|
|
86b702bf46 | ||
|
|
232847b218 | ||
|
|
44dae97784 | ||
|
|
d8e3860e49 | ||
|
|
e01a84b21d | ||
|
|
97c5341bc1 | ||
|
|
de30df446d | ||
|
|
b5b45c8474 | ||
|
|
c5483276e5 | ||
|
|
263b1cb187 | ||
|
|
203add15e5 | ||
|
|
7139ad013d |
5
.devcontainer/devcontainer.json
Normal file
5
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"image": "mcr.microsoft.com/devcontainers/universal:2",
|
||||
"features": {
|
||||
}
|
||||
}
|
||||
33
.github/workflows/patterns.yaml
vendored
Normal file
33
.github/workflows/patterns.yaml
vendored
Normal 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
|
||||
@@ -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]"
|
||||
@@ -57,11 +63,21 @@ jobs:
|
||||
- name: Update version.nix file
|
||||
run: |
|
||||
echo "\"${{ env.new_version }}\"" > pkgs/fabric/version.nix
|
||||
|
||||
- name: Format source codes
|
||||
run: |
|
||||
go fmt ./...
|
||||
|
||||
- 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
|
||||
git add .
|
||||
if ! git diff --staged --quiet; then
|
||||
git commit -m "Update version to ${{ env.new_tag }} and commit $commit_hash"
|
||||
else
|
||||
|
||||
168
.gitignore
vendored
168
.gitignore
vendored
@@ -22,7 +22,7 @@ dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
@@ -166,3 +166,169 @@ cython_debug/
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
web/node_modules
|
||||
|
||||
# Output
|
||||
web/.output
|
||||
web/.vercel
|
||||
web/.svelte-kit
|
||||
web/build
|
||||
|
||||
# OS
|
||||
web/.DS_Store
|
||||
web/Thumbs.db
|
||||
|
||||
# Env
|
||||
web/.env
|
||||
web/.env.*
|
||||
web/!.env.example
|
||||
web/!.env.test
|
||||
|
||||
# Vite
|
||||
web/vite.config.js.timestamp-*
|
||||
web/vite.config.ts.timestamp-*
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/node
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=node
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
web/logs
|
||||
web/*.log
|
||||
web/npm-debug.log*
|
||||
web/yarn-debug.log*
|
||||
web/yarn-error.log*
|
||||
web/lerna-debug.log*
|
||||
web/.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
web/report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
web/pids
|
||||
web/*.pid
|
||||
web/*.seed
|
||||
web/*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
web/lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
web/coverage
|
||||
web/*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
web/.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
web/.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
web/bower_components
|
||||
|
||||
# node-waf configuration
|
||||
web/.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
web/node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web/web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
web/.npm
|
||||
|
||||
# Optional eslint cache
|
||||
web/.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
web/.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
web/.rpt2_cache/
|
||||
web/.rts2_cache_cjs/
|
||||
web/.rts2_cache_es/
|
||||
web/.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
web/.env
|
||||
web/.env.development.local
|
||||
web/.env.test.local
|
||||
web/.env.production.local
|
||||
web/.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
web/.next
|
||||
web/out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
web/.nuxt
|
||||
web/dist
|
||||
|
||||
# Gatsby files
|
||||
web/.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
web/.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
web/.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
web/.vscode-test
|
||||
|
||||
# yarn v2
|
||||
web/.yarn/cache
|
||||
web/.yarn/unplugged
|
||||
web/.yarn/build-state.yml
|
||||
web/.yarn/install-state.gz
|
||||
web/.pnp.*
|
||||
|
||||
### Node Patch ###
|
||||
# Serverless Webpack directories
|
||||
web/.webpack/
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
# SvelteKit build / generate output
|
||||
web/.svelte-kit
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/node
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Use official golang image as builder
|
||||
FROM golang:1.22.5-alpine AS builder
|
||||
FROM golang:1.23.3-alpine AS builder
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
@@ -38,4 +38,4 @@ EXPOSE 8080
|
||||
|
||||
# Run the binary with debug output
|
||||
ENTRYPOINT ["/fabric"]
|
||||
CMD ["--serve"]
|
||||
CMD ["--serve"]
|
||||
|
||||
178
README.md
178
README.md
@@ -25,51 +25,50 @@
|
||||
[Helper Apps](#helper-apps) •
|
||||
[Meta](#meta)
|
||||
|
||||

|
||||
|
||||
</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)
|
||||
- [Web Interface](#Web_Interface)
|
||||
- [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**
|
||||
|
||||
## 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)
|
||||
> 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."`
|
||||
|
||||
## What and why
|
||||
|
||||
@@ -81,6 +80,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**.
|
||||
@@ -149,6 +157,7 @@ go install github.com/danielmiessler/fabric@latest
|
||||
You may need to set some environment variables in your `~/.bashrc` on linux or `~/.zshrc` file on mac to be able to run the `fabric` command. Here is an example of what you can add:
|
||||
|
||||
For Intel based macs or linux
|
||||
|
||||
```bash
|
||||
# Golang environment variables
|
||||
export GOROOT=/usr/local/go
|
||||
@@ -159,6 +168,7 @@ export PATH=$GOPATH/bin:$GOROOT/bin:$HOME/.local/bin:$PATH
|
||||
```
|
||||
|
||||
for Apple Silicon based macs
|
||||
|
||||
```bash
|
||||
# Golang environment variables
|
||||
export GOROOT=$(brew --prefix go)/libexec
|
||||
@@ -167,13 +177,85 @@ export PATH=$GOPATH/bin:$GOROOT/bin:$HOME/.local/bin:$PATH
|
||||
```
|
||||
|
||||
### Setup
|
||||
|
||||
Now run the following command
|
||||
|
||||
```bash
|
||||
# Run the setup to set up your directories and keys
|
||||
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
|
||||
|
||||
@@ -196,11 +278,13 @@ Then [set your environmental variables](#environmental-variables) as shown above
|
||||
### Upgrading
|
||||
|
||||
The great thing about Go is that it's super easy to upgrade. Just run the same command you used to install it in the first place and you'll always get the latest version.
|
||||
|
||||
```bash
|
||||
go install github.com/danielmiessler/fabric@latest
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Once you have it all set up, here's how to use it.
|
||||
|
||||
```bash
|
||||
@@ -248,6 +332,7 @@ Application Options:
|
||||
--printcontext= Print context
|
||||
--printsession= Print session
|
||||
--readability Convert HTML input into a clean, readable view
|
||||
--serve Initiate the API server
|
||||
--dry-run Show what would be sent to the model without actually sending it
|
||||
--version Print current version
|
||||
|
||||
@@ -329,7 +414,6 @@ When you're ready to use them, copy them into:
|
||||
|
||||
You can then use them like any other Patterns, but they won't be public unless you explicitly submit them as Pull Requests to the Fabric project. So don't worry—they're private to you.
|
||||
|
||||
|
||||
This feature works with all openai and ollama models but does NOT work with claude. You can specify your model with the -m flag
|
||||
|
||||
## Helper Apps
|
||||
@@ -387,6 +471,29 @@ You can also create an alias by editing `~/.bashrc` or `~/.zshrc` and adding the
|
||||
alias pbpaste='xclip -selection clipboard -o'
|
||||
```
|
||||
|
||||
## Web Interface
|
||||
|
||||
Fabric now includes a built-in web interface that provides a GUI alternative to the command-line interface and an out-of-the-box website for those who want to get started with web development or blogging.
|
||||
You can use this app as a GUI interface for Fabric, a ready to go blog-site, or a website template for your own projects.
|
||||
|
||||
The `web/src/lib/content` directory includes starter `.obsidian/` and `templates/` directories, allowing you to open up the `web/src/lib/content/` directory as an [Obsidian.md](https://obsidian.md) vault. You can place your posts in the posts directory when you're ready to publish.
|
||||
### Installing
|
||||
|
||||
The GUI can be installed by navigating to the `web` directory and using `npm install`, `pnpm install`, or your favorite package manager. Then simply run the development server to start the app.
|
||||
|
||||
_You will need to run fabric in a separate terminal with the `fabric --serve` command._
|
||||
|
||||
**From the fabric project `web/` directory:**
|
||||
```shell
|
||||
npm run dev
|
||||
|
||||
## or ##
|
||||
|
||||
pnpm run dev
|
||||
|
||||
## or your equivalent
|
||||
```
|
||||
|
||||
## Meta
|
||||
|
||||
> [!NOTE]
|
||||
@@ -395,6 +502,7 @@ alias pbpaste='xclip -selection clipboard -o'
|
||||
- _Jonathan Dunn_ for being the absolute MVP dev on the project, including spearheading the new Go version, as well as the GUI! All this while also being a full-time medical doctor!
|
||||
- _Caleb Sima_ for pushing me over the edge of whether to make this a public project or not.
|
||||
- _Eugen Eisler_ and _Frederick Ros_ for their invaluable contributions to the Go version
|
||||
- _David Peters_ for his work on the web interface.
|
||||
- _Joel Parish_ for super useful input on the project's Github directory structure..
|
||||
- _Joseph Thacker_ for the idea of a `-c` context flag that adds pre-created context in the `./config/fabric/` directory to all Pattern queries.
|
||||
- _Jason Haddix_ for the idea of a stitch (chained Pattern) to filter content using a local model before sending on to a cloud model, i.e., cleaning customer data using `llama2` before sending on to `gpt-4` for analysis.
|
||||
|
||||
@@ -51,6 +51,7 @@ func Cli(version string) (err error) {
|
||||
}
|
||||
|
||||
if currentFlags.Serve {
|
||||
registry.ConfigureVendors()
|
||||
err = restapi.Serve(registry, currentFlags.ServeAddress)
|
||||
return
|
||||
}
|
||||
|
||||
20
cli/flags.go
20
cli/flags.go
@@ -63,8 +63,6 @@ type Flags struct {
|
||||
|
||||
// Init Initialize flags. returns a Flags struct and an error
|
||||
func Init() (ret *Flags, err error) {
|
||||
var message string
|
||||
|
||||
ret = &Flags{}
|
||||
parser := flags.NewParser(ret, flags.Default)
|
||||
var args []string
|
||||
@@ -75,21 +73,19 @@ func Init() (ret *Flags, err error) {
|
||||
info, _ := os.Stdin.Stat()
|
||||
pipedToStdin := (info.Mode() & os.ModeCharDevice) == 0
|
||||
|
||||
//custom message
|
||||
if len(args) > 0 {
|
||||
ret.Message = AppendMessage(ret.Message, args[len(args)-1])
|
||||
}
|
||||
|
||||
// takes input from stdin if it exists, otherwise takes input from args (the last argument)
|
||||
if pipedToStdin {
|
||||
//fmt.Printf("piped: %v\n", args)
|
||||
if message, err = readStdin(); err != nil {
|
||||
var pipedMessage string
|
||||
if pipedMessage, 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 = AppendMessage(ret.Message, pipedMessage)
|
||||
}
|
||||
ret.Message = message
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
73
common/utils.go
Normal file
73
common/utils.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetAbsolutePath resolves a given path to its absolute form, handling ~, ./, ../, UNC paths, and symlinks.
|
||||
func GetAbsolutePath(path string) (string, error) {
|
||||
if path == "" {
|
||||
return "", errors.New("path is empty")
|
||||
}
|
||||
|
||||
// Handle UNC paths on Windows
|
||||
if runtime.GOOS == "windows" && strings.HasPrefix(path, `\\`) {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// Handle ~ for home directory expansion
|
||||
if strings.HasPrefix(path, "~") {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", errors.New("could not resolve home directory")
|
||||
}
|
||||
path = filepath.Join(home, path[1:])
|
||||
}
|
||||
|
||||
// Convert to absolute path
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", errors.New("could not get absolute path")
|
||||
}
|
||||
|
||||
// Resolve symlinks, but allow non-existent paths
|
||||
resolvedPath, err := filepath.EvalSymlinks(absPath)
|
||||
if err == nil {
|
||||
return resolvedPath, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
// Return the absolute path for non-existent paths
|
||||
return absPath, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("could not resolve symlinks: %w", err)
|
||||
}
|
||||
|
||||
// Helper function to check if a symlink points to a directory
|
||||
func IsSymlinkToDir(path string) bool {
|
||||
fileInfo, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if fileInfo.Mode()&os.ModeSymlink != 0 {
|
||||
resolvedPath, err := filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
fileInfo, err = os.Stat(resolvedPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return fileInfo.IsDir()
|
||||
}
|
||||
|
||||
return false // Regular directories should not be treated as symlinks
|
||||
}
|
||||
@@ -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"
|
||||
@@ -30,6 +31,15 @@ func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (s
|
||||
return
|
||||
}
|
||||
|
||||
vendorMessages := session.GetVendorMessages()
|
||||
if len(vendorMessages) == 0 {
|
||||
if session.Name != "" {
|
||||
err = o.db.Sessions.SaveSession(session)
|
||||
}
|
||||
err = fmt.Errorf("no messages provided")
|
||||
return
|
||||
}
|
||||
|
||||
if opts.Model == "" {
|
||||
opts.Model = o.model
|
||||
}
|
||||
@@ -73,6 +83,7 @@ func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (s
|
||||
}
|
||||
|
||||
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 +99,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,17 +110,31 @@ func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *
|
||||
contextContent = ctx.Content
|
||||
}
|
||||
|
||||
// Process any template variables in the message content (user input)
|
||||
// Double curly braces {{variable}} indicate template substitution
|
||||
// Ensure we have a message before processing, other wise we'll get an error when we pass to pattern.go
|
||||
if request.Message == nil {
|
||||
request.Message = &goopenai.ChatCompletionMessage{
|
||||
Role: goopenai.ChatMessageRoleUser,
|
||||
Content: " ",
|
||||
}
|
||||
}
|
||||
|
||||
// Now we know request.Message is not nil, process template variables
|
||||
request.Message.Content, err = template.ApplyTemplate(request.Message.Content, request.PatternVariables, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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 pattern.Pattern != "" {
|
||||
patternContent = pattern.Pattern
|
||||
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)
|
||||
@@ -119,7 +145,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 != "" {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
fabric = pkgs.callPackage ./pkgs/fabric {
|
||||
inherit (gomod2nix.legacyPackages.${system}) buildGoApplication;
|
||||
};
|
||||
inherit (gomod2nix.legacyPackages.${system}) gomod2nix;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
61
go.mod
61
go.mod
@@ -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
128
go.sum
@@ -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=
|
||||
|
||||
113
gomod2nix.toml
113
gomod2nix.toml
@@ -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
BIN
images/fabric-summarize.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 491 KiB |
22
patterns/analyze_candidates/system.md
Normal file
22
patterns/analyze_candidates/system.md
Normal 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:
|
||||
0
patterns/analyze_candidates/user.md
Normal file
0
patterns/analyze_candidates/user.md
Normal file
33
patterns/analyze_mistakes/system.md
Normal file
33
patterns/analyze_mistakes/system.md
Normal 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:
|
||||
|
||||
27
patterns/create_diy/system.md
Normal file
27
patterns/create_diy/system.md
Normal 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:
|
||||
20
patterns/create_newsletter_entry/system.md
Normal file
20
patterns/create_newsletter_entry/system.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Identity and Purpose
|
||||
You are a custom GPT designed to create newsletter sections in the style of Frontend Weekly.
|
||||
|
||||
# Step-by-Step Process:
|
||||
1. The user will provide article text.
|
||||
2. Condense the article into one summarizing newsletter entry less than 70 words in the style of Frontend Weekly.
|
||||
3. Generate a concise title for the entry, focus on the main idea or most important fact of the article
|
||||
|
||||
# Tone and Style Guidelines:
|
||||
* Third-Party Narration: The newsletter should sound like it’s being narrated by an outside observer, someone who is both knowledgeable, unbiased and calm. Focus on the facts or main opinions in the original article. Creates a sense of objectivity and adds a layer of professionalism.
|
||||
|
||||
* Concise: Maintain brevity and clarity. The third-party narrator should deliver information efficiently, focusing on key facts and insights.
|
||||
|
||||
# Output Instructions:
|
||||
Your final output should be a polished, newsletter-ready paragraph with a title line in bold followed by the summary paragraph.
|
||||
|
||||
# INPUT:
|
||||
|
||||
INPUT:
|
||||
|
||||
0
patterns/create_newsletter_entry/user.md
Normal file
0
patterns/create_newsletter_entry/user.md
Normal file
14
patterns/extract_recipe/README.md
Normal file
14
patterns/extract_recipe/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# extract_ctf_writeup
|
||||
|
||||
<h4><code>extract_ctf_writeup</code> is a <a href="https://github.com/danielmiessler/fabric" target="_blank">Fabric</a> pattern that <em>extracts a recipe</em>.</h4>
|
||||
|
||||
|
||||
## Description
|
||||
|
||||
This pattern is used to create a short recipe, consisting of two parts:
|
||||
- A list of ingredients
|
||||
- A step by step guide on how to prepare the meal
|
||||
|
||||
## Meta
|
||||
|
||||
- **Author**: Martin Riedel
|
||||
36
patterns/extract_recipe/system.md
Normal file
36
patterns/extract_recipe/system.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# IDENTITY and PURPOSE
|
||||
|
||||
You are a passionate chef. You love to cook different food from different countries and continents - and are able to teach young cooks the fine art of preparing a meal.
|
||||
|
||||
|
||||
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 short description of the meal. It should be at most three sentences. Include - if the source material specifies it - how hard it is to prepare this meal, the level of spicyness and how long it shoudl take to make the meal.
|
||||
|
||||
- List the INGREDIENTS. Include the measurements.
|
||||
|
||||
- List the Steps that are necessary to prepare the meal.
|
||||
|
||||
|
||||
|
||||
# OUTPUT INSTRUCTIONS
|
||||
|
||||
- Only output Markdown.
|
||||
|
||||
- Do not give warnings or notes; only output the requested sections.
|
||||
|
||||
- You use bulleted lists for output, not numbered lists.
|
||||
|
||||
- Do not repeat ideas, quotes, facts, or resources.
|
||||
|
||||
- Do not start items with the same opening words.
|
||||
|
||||
- Stick to the measurements, do not alter it.
|
||||
|
||||
- Ensure you follow ALL these instructions when creating your output.
|
||||
|
||||
# INPUT
|
||||
|
||||
INPUT:
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
49
patterns/summarize_meeting/system.md
Normal file
49
patterns/summarize_meeting/system.md
Normal 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:
|
||||
@@ -1 +1 @@
|
||||
"1.4.92"
|
||||
"1.4.119"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
15
plugins/ai/gemini_openai/gemini.go
Normal file
15
plugins/ai/gemini_openai/gemini.go
Normal 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
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -5,6 +5,9 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/danielmiessler/fabric/plugins/template"
|
||||
)
|
||||
|
||||
type PatternsEntity struct {
|
||||
@@ -13,7 +16,67 @@ 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
|
||||
}
|
||||
|
||||
// GetApplyVariables main entry point for getting patterns from any source
|
||||
func (o *PatternsEntity) GetApplyVariables(
|
||||
source string, variables map[string]string, input string) (pattern *Pattern, err error) {
|
||||
|
||||
// Determine if this is a file path
|
||||
isFilePath := strings.HasPrefix(source, "\\") ||
|
||||
strings.HasPrefix(source, "/") ||
|
||||
strings.HasPrefix(source, "~") ||
|
||||
strings.HasPrefix(source, ".")
|
||||
|
||||
if isFilePath {
|
||||
// Resolve the file path using GetAbsolutePath
|
||||
absPath, err := common.GetAbsolutePath(source)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not resolve file path: %v", err)
|
||||
}
|
||||
|
||||
// Use the resolved absolute path to get the pattern
|
||||
pattern, err = o.getFromFile(absPath)
|
||||
} else {
|
||||
// Otherwise, get the pattern from the database
|
||||
pattern, err = o.getFromDB(source)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Apply variables to the pattern
|
||||
err = o.applyVariables(pattern, variables, input)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *PatternsEntity) applyVariables(
|
||||
pattern *Pattern, variables map[string]string, input string) (err 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}}"
|
||||
}
|
||||
|
||||
var result string
|
||||
if result, err = template.ApplyTemplate(pattern.Pattern, variables, input); err != nil {
|
||||
return
|
||||
}
|
||||
pattern.Pattern = result
|
||||
return
|
||||
}
|
||||
|
||||
// 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 +92,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 +109,32 @@ 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 *Pattern, err error) {
|
||||
// Handle home directory expansion
|
||||
if strings.HasPrefix(pathStr, "~/") {
|
||||
var homedir string
|
||||
if homedir, err = os.UserHomeDir(); err != nil {
|
||||
err = fmt.Errorf("could not get home directory: %v", err)
|
||||
return
|
||||
}
|
||||
pathStr = filepath.Join(homedir, pathStr[2:])
|
||||
}
|
||||
|
||||
var content []byte
|
||||
if content, err = os.ReadFile(pathStr); err != nil {
|
||||
err = fmt.Errorf("could not read pattern file %s: %v", pathStr, err)
|
||||
return
|
||||
}
|
||||
pattern = &Pattern{
|
||||
Name: pathStr,
|
||||
Pattern: string(content),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get required for Storage interface
|
||||
func (o *PatternsEntity) Get(name string) (*Pattern, error) {
|
||||
// Use GetPattern with no variables
|
||||
return o.GetApplyVariables(name, nil, "")
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
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, tt.pattern.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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,7 @@ func (o *Session) Append(messages ...*goopenai.ChatCompletionMessage) {
|
||||
}
|
||||
|
||||
func (o *Session) GetVendorMessages() (ret []*goopenai.ChatCompletionMessage) {
|
||||
if o.vendorMessages == nil {
|
||||
o.vendorMessages = []*goopenai.ChatCompletionMessage{}
|
||||
if len(o.vendorMessages) == 0 {
|
||||
for _, message := range o.Messages {
|
||||
o.appendVendorMessage(message)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
)
|
||||
|
||||
type StorageEntity struct {
|
||||
@@ -26,37 +26,44 @@ func (o *StorageEntity) Configure() (err error) {
|
||||
|
||||
// GetNames finds all patterns in the patterns directory and enters the id, name, and pattern into a slice of Entry structs. it returns these entries or an error
|
||||
func (o *StorageEntity) GetNames() (ret []string, err error) {
|
||||
var entries []os.DirEntry
|
||||
if entries, err = os.ReadDir(o.Dir); err != nil {
|
||||
err = fmt.Errorf("could not read items from directory: %v", err)
|
||||
return
|
||||
// Resolve the directory path to an absolute path
|
||||
absDir, err := common.GetAbsolutePath(o.Dir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not resolve directory path: %v", err)
|
||||
}
|
||||
|
||||
if o.ItemIsDir {
|
||||
ret = lo.FilterMap(entries, func(item os.DirEntry, index int) (ret string, ok bool) {
|
||||
if ok = item.IsDir(); ok {
|
||||
ret = item.Name()
|
||||
// Read the directory entries
|
||||
var entries []os.DirEntry
|
||||
if entries, err = os.ReadDir(absDir); err != nil {
|
||||
return nil, fmt.Errorf("could not read items from directory: %v", err)
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
entryPath := filepath.Join(absDir, entry.Name())
|
||||
|
||||
// Get metadata for the entry, including symlink info
|
||||
fileInfo, err := os.Lstat(entryPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not stat entry %s: %v", entryPath, err)
|
||||
}
|
||||
|
||||
// Determine if the entry should be included
|
||||
if o.ItemIsDir {
|
||||
// Include directories or symlinks to directories
|
||||
if fileInfo.IsDir() || (fileInfo.Mode()&os.ModeSymlink != 0 && common.IsSymlinkToDir(entryPath)) {
|
||||
ret = append(ret, entry.Name())
|
||||
}
|
||||
return
|
||||
})
|
||||
} else {
|
||||
if o.FileExtension == "" {
|
||||
ret = lo.FilterMap(entries, func(item os.DirEntry, index int) (ret string, ok bool) {
|
||||
if ok = !item.IsDir(); ok {
|
||||
ret = item.Name()
|
||||
}
|
||||
return
|
||||
})
|
||||
} else {
|
||||
ret = lo.FilterMap(entries, func(item os.DirEntry, index int) (ret string, ok bool) {
|
||||
if ok = !item.IsDir() && filepath.Ext(item.Name()) == o.FileExtension; ok {
|
||||
ret = strings.TrimSuffix(item.Name(), o.FileExtension)
|
||||
// Include files, optionally filtering by extension
|
||||
if !fileInfo.IsDir() {
|
||||
if o.FileExtension == "" || filepath.Ext(entry.Name()) == o.FileExtension {
|
||||
ret = append(ret, strings.TrimSuffix(entry.Name(), o.FileExtension))
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (o *StorageEntity) Delete(name string) (err error) {
|
||||
|
||||
418
plugins/template/README.md
Normal file
418
plugins/template/README.md
Normal 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
|
||||
144
plugins/template/datetime.go
Normal file
144
plugins/template/datetime.go
Normal 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
|
||||
}
|
||||
41
plugins/template/datetime.md
Normal file
41
plugins/template/datetime.md
Normal 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}}
|
||||
```
|
||||
138
plugins/template/datetime_test.go
Normal file
138
plugins/template/datetime_test.go
Normal 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
134
plugins/template/fetch.go
Normal 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
39
plugins/template/fetch.md
Normal 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
|
||||
72
plugins/template/fetch_test.go
Normal file
72
plugins/template/fetch_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
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
197
plugins/template/file.go
Normal 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
51
plugins/template/file.md
Normal 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
|
||||
152
plugins/template/file_test.go
Normal file
152
plugins/template/file_test.go
Normal 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
87
plugins/template/sys.go
Normal 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
43
plugins/template/sys.md
Normal 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
|
||||
140
plugins/template/sys_test.go
Normal file
140
plugins/template/sys_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
119
plugins/template/template.go
Normal file
119
plugins/template/template.go
Normal 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
|
||||
}
|
||||
145
plugins/template/template_test.go
Normal file
145
plugins/template/template_test.go
Normal file
@@ -0,0 +1,145 @@
|
||||
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
64
plugins/template/text.go
Normal 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
0
plugins/template/text.md
Normal file
104
plugins/template/text_test.go
Normal file
104
plugins/template/text_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -59,8 +59,7 @@ func (o *YouTube) GetVideoOrPlaylistId(url string) (videoId string, playlistId s
|
||||
}
|
||||
|
||||
// Video ID pattern
|
||||
//https:((youtu.be/7qZl_5xHoBw
|
||||
videoPattern := `(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})`
|
||||
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 {
|
||||
|
||||
211
restapi/chat.go
Executable file
211
restapi/chat.go
Executable file
@@ -0,0 +1,211 @@
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
goopenai "github.com/sashabaranov/go-openai"
|
||||
|
||||
"github.com/danielmiessler/fabric/common"
|
||||
"github.com/danielmiessler/fabric/core"
|
||||
"github.com/danielmiessler/fabric/plugins/db/fsdb"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ChatHandler struct {
|
||||
registry *core.PluginRegistry
|
||||
db *fsdb.Db
|
||||
}
|
||||
|
||||
type PromptRequest struct {
|
||||
UserInput string `json:"userInput"`
|
||||
Vendor string `json:"vendor"`
|
||||
Model string `json:"model"`
|
||||
ContextName string `json:"contextName"`
|
||||
PatternName string `json:"patternName"`
|
||||
}
|
||||
|
||||
type ChatRequest struct {
|
||||
Prompts []PromptRequest `json:"prompts"`
|
||||
common.ChatOptions // Embed the ChatOptions from common package
|
||||
}
|
||||
|
||||
type StreamResponse struct {
|
||||
Type string `json:"type"` // "content", "error", "complete"
|
||||
Format string `json:"format"` // "markdown", "mermaid", "plain"
|
||||
Content string `json:"content"` // The actual content
|
||||
}
|
||||
|
||||
func NewChatHandler(r *gin.Engine, registry *core.PluginRegistry, db *fsdb.Db) *ChatHandler {
|
||||
handler := &ChatHandler{
|
||||
registry: registry,
|
||||
db: db,
|
||||
}
|
||||
|
||||
r.POST("/chat", handler.HandleChat)
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
func (h *ChatHandler) HandleChat(c *gin.Context) {
|
||||
var request ChatRequest
|
||||
|
||||
if err := c.BindJSON(&request); err != nil {
|
||||
log.Printf("Error binding JSON: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid request format: %v", err)})
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Received chat request with %d prompts", len(request.Prompts))
|
||||
|
||||
// Set headers for SSE
|
||||
c.Writer.Header().Set("Content-Type", "text/readystream")
|
||||
c.Writer.Header().Set("Cache-Control", "no-cache")
|
||||
c.Writer.Header().Set("Connection", "keep-alive")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173")
|
||||
c.Writer.Header().Set("X-Accel-Buffering", "no")
|
||||
|
||||
clientGone := c.Writer.CloseNotify()
|
||||
|
||||
for i, prompt := range request.Prompts {
|
||||
select {
|
||||
case <-clientGone:
|
||||
log.Printf("Client disconnected")
|
||||
return
|
||||
default:
|
||||
log.Printf("Processing prompt %d: Model=%s Pattern=%s Context=%s",
|
||||
i+1, prompt.Model, prompt.PatternName, prompt.ContextName)
|
||||
|
||||
// Create chat channel for streaming
|
||||
streamChan := make(chan string)
|
||||
|
||||
// Start chat processing in goroutine
|
||||
go func(p PromptRequest) {
|
||||
defer close(streamChan)
|
||||
|
||||
chatter, err := h.registry.GetChatter(p.Model, 2048, false, false)
|
||||
if err != nil {
|
||||
log.Printf("Error creating chatter: %v", err)
|
||||
streamChan <- fmt.Sprintf("Error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
chatReq := &common.ChatRequest{
|
||||
Message: &goopenai.ChatCompletionMessage{
|
||||
Role: "user",
|
||||
Content: p.UserInput,
|
||||
},
|
||||
PatternName: p.PatternName,
|
||||
ContextName: p.ContextName,
|
||||
}
|
||||
|
||||
opts := &common.ChatOptions{
|
||||
Model: p.Model,
|
||||
Temperature: request.Temperature,
|
||||
TopP: request.TopP,
|
||||
FrequencyPenalty: request.FrequencyPenalty,
|
||||
PresencePenalty: request.PresencePenalty,
|
||||
}
|
||||
|
||||
session, err := chatter.Send(chatReq, opts)
|
||||
if err != nil {
|
||||
log.Printf("Error from chatter.Send: %v", err)
|
||||
streamChan <- fmt.Sprintf("Error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if session == nil {
|
||||
log.Printf("No session returned from chatter.Send")
|
||||
streamChan <- "Error: No response from model"
|
||||
return
|
||||
}
|
||||
|
||||
// Get the last message from the session
|
||||
lastMsg := session.GetLastMessage()
|
||||
if lastMsg != nil {
|
||||
streamChan <- lastMsg.Content
|
||||
} else {
|
||||
log.Printf("No message content in session")
|
||||
streamChan <- "Error: No response content"
|
||||
}
|
||||
}(prompt)
|
||||
|
||||
// Read from streamChan and write to client
|
||||
for content := range streamChan {
|
||||
select {
|
||||
case <-clientGone:
|
||||
return
|
||||
default:
|
||||
if strings.HasPrefix(content, "Error:") {
|
||||
response := StreamResponse{
|
||||
Type: "error",
|
||||
Format: "plain",
|
||||
Content: content,
|
||||
}
|
||||
if err := writeSSEResponse(c.Writer, response); err != nil {
|
||||
log.Printf("Error writing error response: %v", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
response := StreamResponse{
|
||||
Type: "content",
|
||||
Format: detectFormat(content),
|
||||
Content: content,
|
||||
}
|
||||
if err := writeSSEResponse(c.Writer, response); err != nil {
|
||||
log.Printf("Error writing content response: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Signal completion of this prompt
|
||||
completeResponse := StreamResponse{
|
||||
Type: "complete",
|
||||
Format: "plain",
|
||||
Content: "",
|
||||
}
|
||||
if err := writeSSEResponse(c.Writer, completeResponse); err != nil {
|
||||
log.Printf("Error writing completion response: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeSSEResponse(w gin.ResponseWriter, response StreamResponse) error {
|
||||
data, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling response: %v", err)
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintf(w, "data: %s\n\n", string(data)); err != nil {
|
||||
return fmt.Errorf("error writing response: %v", err)
|
||||
}
|
||||
|
||||
w.(http.Flusher).Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
func detectFormat(content string) string {
|
||||
if strings.HasPrefix(content, "graph TD") ||
|
||||
strings.HasPrefix(content, "gantt") ||
|
||||
strings.HasPrefix(content, "flowchart") ||
|
||||
strings.HasPrefix(content, "sequenceDiagram") ||
|
||||
strings.HasPrefix(content, "classDiagram") ||
|
||||
strings.HasPrefix(content, "stateDiagram") {
|
||||
return "mermaid"
|
||||
}
|
||||
if strings.Contains(content, "```") ||
|
||||
strings.Contains(content, "#") ||
|
||||
strings.Contains(content, "*") ||
|
||||
strings.Contains(content, "_") ||
|
||||
strings.Contains(content, "-") {
|
||||
return "markdown"
|
||||
}
|
||||
return "plain"
|
||||
}
|
||||
124
restapi/configuration.go
Executable file
124
restapi/configuration.go
Executable file
@@ -0,0 +1,124 @@
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/danielmiessler/fabric/plugins/db/fsdb"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ConfigHandler defines the handler for configuration-related operations
|
||||
type ConfigHandler struct {
|
||||
db *fsdb.Db
|
||||
// configurations *fsdb.EnvFilePath("$HOME/.config/fabric/.env")
|
||||
}
|
||||
|
||||
func NewConfigHandler(r *gin.Engine, db *fsdb.Db) *ConfigHandler {
|
||||
handler := &ConfigHandler{
|
||||
db: db,
|
||||
// configurations: db.Configurations,
|
||||
}
|
||||
|
||||
r.GET("/config", handler.GetConfig)
|
||||
r.POST("/config/update", handler.UpdateConfig)
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
func (h *ConfigHandler) GetConfig(c *gin.Context) {
|
||||
if h.db == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": ".env file not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if !h.db.IsEnvFileExists() {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"openai": "",
|
||||
"anthropic": "",
|
||||
"groq": "",
|
||||
"mistral": "",
|
||||
"gemini": "",
|
||||
"ollama": "",
|
||||
"openrouter": "",
|
||||
"silicon": "",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err := h.db.LoadEnvFile()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
config := map[string]string{
|
||||
"openai": os.Getenv("OPENAI_API_KEY"),
|
||||
"anthropic": os.Getenv("ANTHROPIC_API_KEY"),
|
||||
"groq": os.Getenv("GROQ_API_KEY"),
|
||||
"mistral": os.Getenv("MISTRAL_API_KEY"),
|
||||
"gemini": os.Getenv("GEMINI_API_KEY"),
|
||||
"ollama": os.Getenv("OLLAMA_URL"),
|
||||
"openrouter": os.Getenv("OPENROUTER_API_KEY"),
|
||||
"silicon": os.Getenv("SILICON_API_KEY"),
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, config)
|
||||
}
|
||||
|
||||
func (h *ConfigHandler) UpdateConfig(c *gin.Context) {
|
||||
if h.db == nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database not initialized"})
|
||||
return
|
||||
}
|
||||
|
||||
var config struct {
|
||||
OpenAIApiKey string `json:"openai_api_key"`
|
||||
AnthropicApiKey string `json:"anthropic_api_key"`
|
||||
GroqApiKey string `json:"groq_api_key"`
|
||||
MistralApiKey string `json:"mistral_api_key"`
|
||||
GeminiApiKey string `json:"gemini_api_key"`
|
||||
OllamaURL string `json:"ollama_url"`
|
||||
OpenRouterApiKey string `json:"openrouter_api_key"`
|
||||
SiliconApiKey string `json:"silicon_api_key"`
|
||||
}
|
||||
|
||||
if err := c.BindJSON(&config); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
envVars := map[string]string{
|
||||
"OPENAI_API_KEY": config.OpenAIApiKey,
|
||||
"ANTHROPIC_API_KEY": config.AnthropicApiKey,
|
||||
"GROQ_API_KEY": config.GroqApiKey,
|
||||
"MISTRAL_API_KEY": config.MistralApiKey,
|
||||
"GEMINI_API_KEY": config.GeminiApiKey,
|
||||
"OLLAMA_URL": config.OllamaURL,
|
||||
"OPENROUTER_API_KEY": config.OpenRouterApiKey,
|
||||
"SILICON_API_KEY": config.SiliconApiKey,
|
||||
}
|
||||
|
||||
var envContent strings.Builder
|
||||
for key, value := range envVars {
|
||||
if value != "" {
|
||||
envContent.WriteString(fmt.Sprintf("%s=%s\n", key, value))
|
||||
os.Setenv(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// Save configuration to file
|
||||
if err := h.db.SaveEnv(envContent.String()); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.LoadEnvFile(); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Configuration updated successfully"})
|
||||
}
|
||||
45
restapi/models.go
Executable file
45
restapi/models.go
Executable file
@@ -0,0 +1,45 @@
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"github.com/danielmiessler/fabric/plugins/ai"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ModelsHandler struct {
|
||||
vendorManager *ai.VendorsManager
|
||||
}
|
||||
|
||||
func NewModelsHandler(r *gin.Engine, vendorManager *ai.VendorsManager) {
|
||||
handler := &ModelsHandler{
|
||||
vendorManager: vendorManager,
|
||||
}
|
||||
|
||||
r.GET("/models/names", handler.GetModelNames)
|
||||
}
|
||||
|
||||
func (h *ModelsHandler) GetModelNames(c *gin.Context) {
|
||||
vendorsModels, err := h.vendorManager.GetModels()
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"error": "Server failed to retrieve model names"})
|
||||
return
|
||||
}
|
||||
|
||||
response := make(map[string]interface{})
|
||||
vendors := make(map[string][]string)
|
||||
|
||||
for _, groupItems := range vendorsModels.GroupsItems {
|
||||
vendors[groupItems.Group] = groupItems.Items
|
||||
}
|
||||
|
||||
response["models"] = h.getAllModelNames(vendorsModels)
|
||||
response["vendors"] = vendors
|
||||
c.JSON(200, response)
|
||||
}
|
||||
|
||||
func (h *ModelsHandler) getAllModelNames(vendorsModels *ai.VendorsModels) []string {
|
||||
var allModelNames []string
|
||||
for _, groupItems := range vendorsModels.GroupsItems {
|
||||
allModelNames = append(allModelNames, groupItems.Items...)
|
||||
}
|
||||
return allModelNames
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
@@ -17,6 +17,9 @@ func Serve(registry *core.PluginRegistry, address string) (err error) {
|
||||
NewPatternsHandler(r, fabricDb.Patterns)
|
||||
NewContextsHandler(r, fabricDb.Contexts)
|
||||
NewSessionsHandler(r, fabricDb.Sessions)
|
||||
NewChatHandler(r, registry, fabricDb)
|
||||
NewConfigHandler(r, fabricDb)
|
||||
NewModelsHandler(r, registry.VendorManager)
|
||||
|
||||
// Start server
|
||||
err = r.Run(address)
|
||||
|
||||
1
stitches/rate_ai_result/rate_ai_result.txt
Normal file
1
stitches/rate_ai_result/rate_ai_result.txt
Normal 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
|
||||
60
stitches/rate_ai_result/readme.md
Normal file
60
stitches/rate_ai_result/readme.md
Normal 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.
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v1.4.92"
|
||||
var version = "v1.4.119"
|
||||
|
||||
11
web/.github/dependabot.yml
vendored
Normal file
11
web/.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm", "pnpm" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
0
web/.npmrc
Normal file
0
web/.npmrc
Normal file
4
web/.prettierignore
Normal file
4
web/.prettierignore
Normal file
@@ -0,0 +1,4 @@
|
||||
# Package Managers
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
8
web/.prettierrc
Normal file
8
web/.prettierrc
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
120
web/.vscode/settings.json
vendored
Normal file
120
web/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"prettier.documentSelectors": [
|
||||
"**/*.svelte"
|
||||
],
|
||||
"tailwindCSS.classAttributes": [
|
||||
"class",
|
||||
"accent",
|
||||
"active",
|
||||
"animIndeterminate",
|
||||
"aspectRatio",
|
||||
"background",
|
||||
"badge",
|
||||
"bgBackdrop",
|
||||
"bgDark",
|
||||
"bgDrawer",
|
||||
"bgLight",
|
||||
"blur",
|
||||
"border",
|
||||
"button",
|
||||
"buttonAction",
|
||||
"buttonBack",
|
||||
"buttonClasses",
|
||||
"buttonComplete",
|
||||
"buttonDismiss",
|
||||
"buttonNeutral",
|
||||
"buttonNext",
|
||||
"buttonPositive",
|
||||
"buttonTextCancel",
|
||||
"buttonTextConfirm",
|
||||
"buttonTextFirst",
|
||||
"buttonTextLast",
|
||||
"buttonTextNext",
|
||||
"buttonTextPrevious",
|
||||
"buttonTextSubmit",
|
||||
"caretClosed",
|
||||
"caretOpen",
|
||||
"chips",
|
||||
"color",
|
||||
"controlSeparator",
|
||||
"controlVariant",
|
||||
"cursor",
|
||||
"display",
|
||||
"element",
|
||||
"fill",
|
||||
"fillDark",
|
||||
"fillLight",
|
||||
"flex",
|
||||
"flexDirection",
|
||||
"gap",
|
||||
"gridColumns",
|
||||
"height",
|
||||
"hover",
|
||||
"inactive",
|
||||
"indent",
|
||||
"justify",
|
||||
"meter",
|
||||
"padding",
|
||||
"position",
|
||||
"regionAnchor",
|
||||
"regionBackdrop",
|
||||
"regionBody",
|
||||
"regionCaption",
|
||||
"regionCaret",
|
||||
"regionCell",
|
||||
"regionChildren",
|
||||
"regionChipList",
|
||||
"regionChipWrapper",
|
||||
"regionCone",
|
||||
"regionContent",
|
||||
"regionControl",
|
||||
"regionDefault",
|
||||
"regionDrawer",
|
||||
"regionFoot",
|
||||
"regionFootCell",
|
||||
"regionFooter",
|
||||
"regionHead",
|
||||
"regionHeadCell",
|
||||
"regionHeader",
|
||||
"regionIcon",
|
||||
"regionInput",
|
||||
"regionInterface",
|
||||
"regionInterfaceText",
|
||||
"regionLabel",
|
||||
"regionLead",
|
||||
"regionLegend",
|
||||
"regionList",
|
||||
"regionListItem",
|
||||
"regionNavigation",
|
||||
"regionPage",
|
||||
"regionPanel",
|
||||
"regionRowHeadline",
|
||||
"regionRowMain",
|
||||
"regionSummary",
|
||||
"regionSymbol",
|
||||
"regionTab",
|
||||
"regionTrail",
|
||||
"ring",
|
||||
"rounded",
|
||||
"select",
|
||||
"shadow",
|
||||
"slotDefault",
|
||||
"slotFooter",
|
||||
"slotHeader",
|
||||
"slotLead",
|
||||
"slotMessage",
|
||||
"slotMeta",
|
||||
"slotPageContent",
|
||||
"slotPageFooter",
|
||||
"slotPageHeader",
|
||||
"slotSidebarLeft",
|
||||
"slotSidebarRight",
|
||||
"slotTrail",
|
||||
"spacing",
|
||||
"text",
|
||||
"track",
|
||||
"transition",
|
||||
"width",
|
||||
"zIndex"
|
||||
]
|
||||
}
|
||||
13
web/README.md
Normal file
13
web/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
## The Fabric Web App
|
||||
[Installing](#Installing)|[Todos](#Todos)|[Collaborators](#Collaborators)
|
||||
|
||||
This is the web app for Fabric. It is built using [Svelte](https://svelte.dev/), [SkeletonUI](https://skeleton.dev/), and [Mdsvex](https://mdsvex.pngwn.io/).
|
||||
|
||||
The goal of this app is to not only provide a user interface for Fabric, but also a out-of-the-box website for those who want to get started with web development or blogging. The tech stack is minimal and (I hope) the code is easy to read and understand. One thing I kept in mind when making this app was to make it easy for beginners to get started with web development. You can use this app as a GUI interface for Fabric, a ready to go blog-site, or a website template for your own projects. I hope you find it useful!
|
||||
|
||||

|
||||
|
||||
### Installing
|
||||
It can be installed by navigating to the `web` directory and using `npm install`, `pnpm install`, or your favorite package manager. Then simply run `npm run dev` or your equivalent command to start the app. *You will need to run fabric in a seperate terminal with the `fabric --serve` command.*
|
||||
|
||||
|
||||
21
web/SECURITY.md
Normal file
21
web/SECURITY.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Use this section to tell people about which versions of your project are
|
||||
currently being supported with security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 5.1.x | :white_check_mark: |
|
||||
| 5.0.x | :x: |
|
||||
| 4.0.x | :white_check_mark: |
|
||||
| < 4.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Use this section to tell people how to report a vulnerability.
|
||||
|
||||
Tell them where to go, how often they can expect to get an update on a
|
||||
reported vulnerability, what to expect if the vulnerability is accepted or
|
||||
declined, etc.
|
||||
38
web/STD-README.md
Normal file
38
web/STD-README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# create-svelte
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npm create svelte@latest
|
||||
|
||||
# create a new project in my-app
|
||||
npm create svelte@latest my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
||||
32
web/eslint.config.js
Normal file
32
web/eslint.config.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import eslint from '@eslint/js';
|
||||
import prettier from 'eslint-config-prettier';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import globals from 'globals';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
...svelte.configs['flat/recommended'],
|
||||
prettier,
|
||||
...svelte.configs['flat/prettier'],
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.svelte'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: tseslint.parser
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
ignores: ['build/', '.svelte-kit/', 'dist/']
|
||||
}
|
||||
);
|
||||
19
web/jsconfig.json
Normal file
19
web/jsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
||||
4
web/markdown.d.ts
vendored
Normal file
4
web/markdown.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/* declare module '*.md' {
|
||||
const component: import('svelte').ComponentType;
|
||||
export default component;
|
||||
} */
|
||||
102
web/my-custom-theme.ts
Normal file
102
web/my-custom-theme.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
|
||||
import type { CustomThemeConfig } from '@skeletonlabs/tw-plugin';
|
||||
|
||||
export const myCustomTheme: CustomThemeConfig = {
|
||||
name: 'my-custom-theme',
|
||||
properties: {
|
||||
// =~= Theme Properties =~=
|
||||
"--theme-font-family-base": `system-ui`,
|
||||
"--theme-font-family-heading": `system-ui`,
|
||||
"--theme-font-color-base": "var(--color-primary-800)",
|
||||
"--theme-font-color-dark": "var(--color-primary-300)",
|
||||
"--theme-rounded-base": "9999px",
|
||||
"--theme-rounded-container": "8px",
|
||||
"--theme-border-base": "1px",
|
||||
// =~= Theme On-X Colors =~=
|
||||
"--on-primary": "0 0 0",
|
||||
"--on-secondary": "0 0 0",
|
||||
"--on-tertiary": "0 0 0",
|
||||
"--on-success": "0 0 0",
|
||||
"--on-warning": "0 0 0",
|
||||
"--on-error": "0 0 0",
|
||||
"--on-surface": "0 0 0",
|
||||
// =~= Theme Colors =~=
|
||||
// primary | #613bf7
|
||||
"--color-primary-50": "231 226 254", // #e7e2fe
|
||||
"--color-primary-100": "223 216 253", // #dfd8fd
|
||||
"--color-primary-200": "216 206 253", // #d8cefd
|
||||
"--color-primary-300": "192 177 252", // #c0b1fc
|
||||
"--color-primary-400": "144 118 249", // #9076f9
|
||||
"--color-primary-500": "97 59 247", // #613bf7
|
||||
"--color-primary-600": "87 53 222", // #5735de
|
||||
"--color-primary-700": "73 44 185", // #492cb9
|
||||
"--color-primary-800": "58 35 148", // #3a2394
|
||||
"--color-primary-900": "48 29 121", // #301d79
|
||||
// secondary | #9de1ae
|
||||
"--color-secondary-50": "240 251 243", // #f0fbf3
|
||||
"--color-secondary-100": "235 249 239", // #ebf9ef
|
||||
"--color-secondary-200": "231 248 235", // #e7f8eb
|
||||
"--color-secondary-300": "216 243 223", // #d8f3df
|
||||
"--color-secondary-400": "186 234 198", // #baeac6
|
||||
"--color-secondary-500": "157 225 174", // #9de1ae
|
||||
"--color-secondary-600": "141 203 157", // #8dcb9d
|
||||
"--color-secondary-700": "118 169 131", // #76a983
|
||||
"--color-secondary-800": "94 135 104", // #5e8768
|
||||
"--color-secondary-900": "77 110 85", // #4d6e55
|
||||
// tertiary | #3fa0a6
|
||||
"--color-tertiary-50": "226 241 242", // #e2f1f2
|
||||
"--color-tertiary-100": "217 236 237", // #d9eced
|
||||
"--color-tertiary-200": "207 231 233", // #cfe7e9
|
||||
"--color-tertiary-300": "178 217 219", // #b2d9db
|
||||
"--color-tertiary-400": "121 189 193", // #79bdc1
|
||||
"--color-tertiary-500": "63 160 166", // #3fa0a6
|
||||
"--color-tertiary-600": "57 144 149", // #399095
|
||||
"--color-tertiary-700": "47 120 125", // #2f787d
|
||||
"--color-tertiary-800": "38 96 100", // #266064
|
||||
"--color-tertiary-900": "31 78 81", // #1f4e51
|
||||
// success | #37b3fc
|
||||
"--color-success-50": "225 244 255", // #e1f4ff
|
||||
"--color-success-100": "215 240 254", // #d7f0fe
|
||||
"--color-success-200": "205 236 254", // #cdecfe
|
||||
"--color-success-300": "175 225 254", // #afe1fe
|
||||
"--color-success-400": "115 202 253", // #73cafd
|
||||
"--color-success-500": "55 179 252", // #37b3fc
|
||||
"--color-success-600": "50 161 227", // #32a1e3
|
||||
"--color-success-700": "41 134 189", // #2986bd
|
||||
"--color-success-800": "33 107 151", // #216b97
|
||||
"--color-success-900": "27 88 123", // #1b587b
|
||||
// warning | #d209f8
|
||||
"--color-warning-50": "248 218 254", // #f8dafe
|
||||
"--color-warning-100": "246 206 254", // #f6cefe
|
||||
"--color-warning-200": "244 194 253", // #f4c2fd
|
||||
"--color-warning-300": "237 157 252", // #ed9dfc
|
||||
"--color-warning-400": "224 83 250", // #e053fa
|
||||
"--color-warning-500": "210 9 248", // #d209f8
|
||||
"--color-warning-600": "189 8 223", // #bd08df
|
||||
"--color-warning-700": "158 7 186", // #9e07ba
|
||||
"--color-warning-800": "126 5 149", // #7e0595
|
||||
"--color-warning-900": "103 4 122", // #67047a
|
||||
// error | #90df16
|
||||
"--color-error-50": "238 250 220", // #eefadc
|
||||
"--color-error-100": "233 249 208", // #e9f9d0
|
||||
"--color-error-200": "227 247 197", // #e3f7c5
|
||||
"--color-error-300": "211 242 162", // #d3f2a2
|
||||
"--color-error-400": "177 233 92", // #b1e95c
|
||||
"--color-error-500": "144 223 22", // #90df16
|
||||
"--color-error-600": "130 201 20", // #82c914
|
||||
"--color-error-700": "108 167 17", // #6ca711
|
||||
"--color-error-800": "86 134 13", // #56860d
|
||||
"--color-error-900": "71 109 11", // #476d0b
|
||||
// surface | #46a1ed
|
||||
"--color-surface-50": "227 241 252", // #e3f1fc
|
||||
"--color-surface-100": "218 236 251", // #daecfb
|
||||
"--color-surface-200": "209 232 251", // #d1e8fb
|
||||
"--color-surface-300": "181 217 248", // #b5d9f8
|
||||
"--color-surface-400": "126 189 242", // #7ebdf2
|
||||
"--color-surface-500": "70 161 237", // #46a1ed
|
||||
"--color-surface-600": "63 145 213", // #3f91d5
|
||||
"--color-surface-700": "53 121 178", // #3579b2
|
||||
"--color-surface-800": "42 97 142", // #2a618e
|
||||
"--color-surface-900": "34 79 116", // #224f74
|
||||
}
|
||||
}
|
||||
7397
web/package-lock.json
generated
Normal file
7397
web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
53
web/package.json
Normal file
53
web/package.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "terminal-blog",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"test": "vitest",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@skeletonlabs/skeleton": "^2.8.0",
|
||||
"@skeletonlabs/tw-plugin": "^0.3.1",
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/kit": "^2.8.4",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@types/node": "^20.10.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"lucide-svelte": "^0.309.0",
|
||||
"mdsvex": "^0.11.0",
|
||||
"postcss": "^8.4.32",
|
||||
"postcss-load-config": "^5.0.2",
|
||||
"svelte": "^4.2.7",
|
||||
"svelte-check": "^3.6.0",
|
||||
"svelte-youtube-embed": "^0.3.3",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^5.0.3",
|
||||
"vite-plugin-tailwind-purgecss": "^0.2.0"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.5.3",
|
||||
"clsx": "^2.1.1",
|
||||
"cn": "^0.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"highlight.js": "^11.10.0",
|
||||
"marked": "^15.0.1",
|
||||
"rehype": "^13.0.2",
|
||||
"rehype-autolink-headings": "^7.1.0",
|
||||
"rehype-external-links": "^3.0.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"rehype-unwrap-images": "^1.0.0",
|
||||
"tailwind-merge": "^2.5.4",
|
||||
"youtube-transcript": "^1.2.1"
|
||||
}
|
||||
}
|
||||
2925
web/pnpm-lock.yaml
generated
Normal file
2925
web/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
6
web/postcss.config.cjs
Normal file
6
web/postcss.config.cjs
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
6
web/postcss.config.js
Normal file
6
web/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
22
web/rollup.config.js
Normal file
22
web/rollup.config.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import svelte from 'rollup-plugin-svelte';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
|
||||
export default {
|
||||
input: 'src/+page.js',
|
||||
output: {
|
||||
file: 'public/bundle.js',
|
||||
format: 'iife',
|
||||
name: 'app'
|
||||
},
|
||||
plugins: [
|
||||
svelte({
|
||||
// svelte options
|
||||
extensions: [".svelte", ".svx", ".md"],
|
||||
preprocess: mdsvex()
|
||||
}),
|
||||
resolve({
|
||||
browser: true,
|
||||
dedupe: ['svelte']
|
||||
})
|
||||
]
|
||||
};
|
||||
134
web/src/app.css
Normal file
134
web/src/app.css
Normal file
@@ -0,0 +1,134 @@
|
||||
/* @tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||||
Cantarell, 'Go Mono', 'Fira Sans', 'Helvetica Neue', sans-serif;
|
||||
--font-mono: 'Fira Code', monospace;
|
||||
--column-width: 42rem;
|
||||
--column-margin-top: 4rem;
|
||||
} */
|
||||
|
||||
/* Light theme variables */
|
||||
/* :root {
|
||||
--background: hsl(249, 81%, 85%);
|
||||
--foreground: hsl(229, 65%, 29%);
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 224 71.4% 4.1%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 224 71.4% 4.1%;
|
||||
--primary: hsl(262.1 83.3% 57.8%);
|
||||
--primary-foreground: hsl(274, 100%, 90%);
|
||||
--secondary: hsl(173, 74%, 68%);
|
||||
--secondary-foreground: hsl(195, 100%, 90%);
|
||||
--muted: 220 14.3% 95.9%;
|
||||
--muted-foreground: 220 8.9% 46.1%;
|
||||
--accent: hsl(220, 37%, 49%);
|
||||
--accent-foreground: 220.9 39.3% 11%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 20% 98%;
|
||||
--border: 220 13% 91%;
|
||||
--input: 220 13% 91%;
|
||||
--ring: 262.1 83.3% 57.8%;
|
||||
--radius: 0.5rem;
|
||||
} */
|
||||
|
||||
/* Dark theme variables */
|
||||
/* .dark {
|
||||
--background: 224 71.4% 4.1%;
|
||||
--foreground: 210 20% 98%;
|
||||
--card: 224 71.4% 4.1%;
|
||||
--card-foreground: 210 20% 98%;
|
||||
--popover: 224 71.4% 4.1%;
|
||||
--popover-foreground: 210 20% 98%;
|
||||
--primary: 263.4 70% 50.4%;
|
||||
--primary-foreground: 210 20% 98%;
|
||||
--secondary: 215 27.9% 16.9%;
|
||||
--secondary-foreground: 210 20% 98%;
|
||||
--muted: 215 27.9% 16.9%;
|
||||
--muted-foreground: 217.9 10.6% 64.9%;
|
||||
--accent: 215 27.9% 16.9%;
|
||||
--accent-foreground: 210 20% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 20% 98%;
|
||||
--border: 215 27.9% 16.9%;
|
||||
--input: 215 27.9% 16.9%;
|
||||
--ring: 263.4 70% 50.4%;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-feature-settings: "rlig" 1, "calt" 1;
|
||||
} */
|
||||
|
||||
/* Enhanced Typography */
|
||||
/* h1, h2, h3, h4, h5, h6 {
|
||||
@apply font-semibold tracking-tight;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-4xl lg:text-5xl;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply text-3xl lg:text-4xl;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-2xl lg:text-3xl;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply leading-7;
|
||||
}
|
||||
*/
|
||||
/* Links */
|
||||
/* a {
|
||||
@apply text-primary hover:text-primary/80 transition-colors;
|
||||
} */
|
||||
|
||||
/* Code blocks */
|
||||
/* pre {
|
||||
@apply p-4 rounded-lg bg-muted/50 font-mono text-sm;
|
||||
}
|
||||
|
||||
code {
|
||||
@apply font-mono text-sm;
|
||||
}
|
||||
*/
|
||||
/* Terminal specific styles */
|
||||
/* .terminal-window {
|
||||
@apply rounded-lg border bg-card shadow-lg overflow-hidden;
|
||||
}
|
||||
|
||||
.terminal-text {
|
||||
@apply font-mono text-sm;
|
||||
}
|
||||
*/
|
||||
/* Form elements */
|
||||
/* input, textarea, select {
|
||||
@apply rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2;
|
||||
}
|
||||
|
||||
button {
|
||||
@apply inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2;
|
||||
}
|
||||
} */
|
||||
|
||||
/* Custom scrollbar */
|
||||
/* ::-webkit-scrollbar {
|
||||
@apply w-2;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
@apply bg-muted;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-muted-foreground/30 rounded-full hover:bg-muted-foreground/50 transition-colors;
|
||||
} */
|
||||
9
web/src/app.d.ts
vendored
Normal file
9
web/src/app.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
// and what to do when importing types
|
||||
declare namespace App {
|
||||
// interface Locals {}
|
||||
interface PageData {}
|
||||
// interface Error {}
|
||||
// interface Platform {}
|
||||
}
|
||||
12
web/src/app.html
Normal file
12
web/src/app.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover" data-theme="my-custom-theme">
|
||||
<div style="display: contents" class="h-full overflow-hidden">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
25
web/src/app.postcss
Normal file
25
web/src/app.postcss
Normal file
@@ -0,0 +1,25 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@tailwind variants;
|
||||
|
||||
html,
|
||||
body {
|
||||
@apply h-full overflow-hidden;
|
||||
}
|
||||
|
||||
.terminal-output {
|
||||
@apply font-mono text-sm;
|
||||
}
|
||||
|
||||
.terminal-input {
|
||||
@apply font-mono text-sm;
|
||||
}
|
||||
|
||||
/* Skeleton theme overrides */
|
||||
:root [data-theme='skeleton'] {
|
||||
--theme-font-family-base: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
--theme-font-family-heading: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
7
web/src/index.test.ts
Normal file
7
web/src/index.test.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
describe('sum test', () => {
|
||||
it('adds 1 + 2 to equal 3', () => {
|
||||
expect(1 + 2).toBe(3);
|
||||
});
|
||||
});
|
||||
26
web/src/lib/components/ui/button/button.svelte
Normal file
26
web/src/lib/components/ui/button/button.svelte
Normal file
@@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/types/utils";
|
||||
import { buttonVariants } from "./index.js";
|
||||
|
||||
let className = undefined;
|
||||
export let variant = "";
|
||||
export let size = "default";
|
||||
export { className as class };
|
||||
|
||||
$: classes = cn(
|
||||
buttonVariants.base,
|
||||
buttonVariants.variants.variant[variant as keyof typeof buttonVariants.variants.variant],
|
||||
buttonVariants.variants.size[size as keyof typeof buttonVariants.variants.size],
|
||||
className
|
||||
);
|
||||
</script>
|
||||
|
||||
<button
|
||||
class={classes}
|
||||
type="button"
|
||||
on:click
|
||||
on:keydown
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</button>
|
||||
31
web/src/lib/components/ui/button/index.js
Normal file
31
web/src/lib/components/ui/button/index.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import Root from "./button.svelte";
|
||||
|
||||
const buttonVariants = {
|
||||
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50",
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90 shadow",
|
||||
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-sm",
|
||||
outline: "border-input bg-background hover:bg-accent hover:text-accent-foreground border shadow-sm",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-sm",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline"
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2",
|
||||
sm: "h-8 rounded-md px-3 text-xs",
|
||||
lg: "h-10 rounded-md px-8",
|
||||
icon: "h-9 w-9"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default"
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
Root,
|
||||
Root as Button,
|
||||
buttonVariants
|
||||
};
|
||||
32
web/src/lib/components/ui/buymeacoffee/BuyMeCoffee.svelte
Normal file
32
web/src/lib/components/ui/buymeacoffee/BuyMeCoffee.svelte
Normal file
@@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
export let url: string = 'https://www.buymeacoffee.com/johnconnor.sec';
|
||||
export let text: string = 'Buy me a coffee';
|
||||
|
||||
</script>
|
||||
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-sm px-3 py-1.5 btn variant-filled-tertiary hover:variant-filled-secondary transition-all duration-200 flex items-center gap-2"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="transition-transform duration-200 group-hover:rotate-12"
|
||||
>
|
||||
<path d="M17 8h1a4 4 0 1 1 0 8h-1" />
|
||||
<path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z" />
|
||||
<line x1="6" y1="2" x2="6" y2="4" />
|
||||
<line x1="10" y1="2" x2="10" y2="4" />
|
||||
<line x1="14" y1="2" x2="14" y2="4" />
|
||||
</svg>
|
||||
{text}
|
||||
</a>
|
||||
6
web/src/lib/components/ui/input/index.ts
Normal file
6
web/src/lib/components/ui/input/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import Root from "./input.svelte";
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Input,
|
||||
};
|
||||
33
web/src/lib/components/ui/input/input.svelte
Normal file
33
web/src/lib/components/ui/input/input.svelte
Normal file
@@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/types/utils";
|
||||
let className: string | undefined = undefined;
|
||||
export let value = undefined;
|
||||
export { className as class };
|
||||
export let readonly = undefined;
|
||||
</script>
|
||||
|
||||
<input
|
||||
class={cn(
|
||||
"border-input placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-sm shadow-lg transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
bind:value
|
||||
{readonly}
|
||||
on:blur
|
||||
on:change
|
||||
on:click
|
||||
on:focus
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:keydown
|
||||
on:keypress
|
||||
on:keyup
|
||||
on:mouseover
|
||||
on:mouseenter
|
||||
on:mouseleave
|
||||
on:mousemove
|
||||
on:paste
|
||||
on:input
|
||||
on:wheel|passive
|
||||
{...$$restProps}
|
||||
/>
|
||||
6
web/src/lib/components/ui/label/index.js
Normal file
6
web/src/lib/components/ui/label/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import Root from "./label.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Root as Label
|
||||
};
|
||||
15
web/src/lib/components/ui/label/label.svelte
Normal file
15
web/src/lib/components/ui/label/label.svelte
Normal file
@@ -0,0 +1,15 @@
|
||||
<script>
|
||||
import { cn } from "$lib/types/utils.ts";
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<label
|
||||
class={cn(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</label>
|
||||
6
web/src/lib/components/ui/select/index.js
Normal file
6
web/src/lib/components/ui/select/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import Root from "./select.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Root as Select
|
||||
};
|
||||
30
web/src/lib/components/ui/select/select-content.svelte
Normal file
30
web/src/lib/components/ui/select/select-content.svelte
Normal file
@@ -0,0 +1,30 @@
|
||||
<script>
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
import { getContext } from "svelte";
|
||||
|
||||
let className = undefined;
|
||||
export let sideOffset = 4;
|
||||
export let position = "popper";
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={cn(
|
||||
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<SelectPrimitive.Content {position} {sideOffset} {...$$restProps}>
|
||||
<div
|
||||
class={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||
)}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</SelectPrimitive.Content>
|
||||
</div>
|
||||
16
web/src/lib/components/ui/select/select-item.svelte
Normal file
16
web/src/lib/components/ui/select/select-item.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
let className = undefined;
|
||||
export let value;
|
||||
export let disabled = false;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<option
|
||||
{value}
|
||||
{disabled}
|
||||
class={cn("relative flex w-full cursor-default select-none py-1.5 pl-2 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className)}
|
||||
>
|
||||
<slot />
|
||||
</option>
|
||||
10
web/src/lib/components/ui/select/select-label.svelte
Normal file
10
web/src/lib/components/ui/select/select-label.svelte
Normal file
@@ -0,0 +1,10 @@
|
||||
<script>
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/types/utils.ts";
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Label class={cn("px-2 py-1.5 text-sm variant-filled-secondary font-semibold", className)} {...$$restProps}>
|
||||
<slot />
|
||||
</SelectPrimitive.Label>
|
||||
8
web/src/lib/components/ui/select/select-separator.svelte
Normal file
8
web/src/lib/components/ui/select/select-separator.svelte
Normal file
@@ -0,0 +1,8 @@
|
||||
<script>
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/types/utils.ts";
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Separator class={cn("bg-muted -mx-1 my-1 h-px", className)} {...$$restProps} />
|
||||
20
web/src/lib/components/ui/select/select-trigger.svelte
Normal file
20
web/src/lib/components/ui/select/select-trigger.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script>
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { ChevronDown } from "lucide-svelte";
|
||||
import { cn } from "$lib/utils";
|
||||
import { getContext } from "svelte";
|
||||
|
||||
let className = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Trigger
|
||||
class={cn(
|
||||
"flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
<ChevronDown class="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Trigger>
|
||||
18
web/src/lib/components/ui/select/select-value.svelte
Normal file
18
web/src/lib/components/ui/select/select-value.svelte
Normal file
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type $$Props = SelectPrimitive.ValueProps;
|
||||
|
||||
let className: $$Props["class"] = undefined;
|
||||
export { className as class };
|
||||
export let placeholder: $$Props["placeholder"] = undefined;
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Value
|
||||
class={cn("text-sm", className)}
|
||||
{placeholder}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</SelectPrimitive.Value>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user