Compare commits

...

203 Commits

Author SHA1 Message Date
github-actions[bot]
1b7a1fa652 Update version to v1.4.67 and commit 2024-10-19 11:10:23 +00:00
Eugen Eisler
8a3d63ef48 Merge remote-tracking branch 'origin/main' 2024-10-19 13:10:09 +02:00
Eugen Eisler
609df943dd feat: plugins arch., new setup procedure 2024-10-19 13:09:58 +02:00
github-actions[bot]
8941551f5a Update version to v1.4.66 and commit 2024-10-19 11:09:55 +00:00
Eugen Eisler
61f66f88e3 feat: plugins arch., new setup procedure 2024-10-19 13:09:37 +02:00
github-actions[bot]
15de33107b Update version to v1.4.65 and commit 2024-10-16 13:27:28 +00:00
Eugen Eisler
81f9b1dabb Merge pull request #1045 from Fenicio/patch-1
Update patterns/analyze_answers/system.md - Fixed a bunch of typos
2024-10-16 16:27:14 +03:00
Guillermo G C
888342c119 Update patterns/analyze_answers/system.md - Fixed a bunch of typos 2024-10-15 08:53:26 +02:00
github-actions[bot]
12d83dad6d Update version to v1.4.64 and commit 2024-10-14 18:17:37 +00:00
Jonathan Dunn
14ef9fd41c updated readme 2024-10-14 14:17:19 -04:00
github-actions[bot]
584e0c8547 Update version to v1.4.63 and commit 2024-10-13 14:42:39 +00:00
Daniel Miessler
914b312c2e Merge pull request #862 from Thepathakarpit/patch-1
Create setup_fabric.bat, a batch script to automate setup and running…
2024-10-13 07:42:27 -07:00
github-actions[bot]
8153d690cc Update version to v1.4.62 and commit 2024-10-13 14:40:48 +00:00
Daniel Miessler
d03bdbeb9b Merge pull request #1044 from danielmiessler/feat/rest-api
Feat/rest api
2024-10-13 07:40:32 -07:00
github-actions[bot]
87730043b5 Update version to v1.4.61 and commit 2024-10-13 02:50:02 +00:00
Daniel Miessler
3285b8e330 Updated extract sponsors. 2024-10-12 19:49:44 -07:00
Eugen Eisler
686d039392 Merge branch 'main' into feat/rest-api 2024-10-12 23:06:29 +03:00
Eugen Eisler
d7683e4c39 feat: restructure for better reuse 2024-10-12 22:49:26 +03:00
Eugen Eisler
56f995afb4 feat: restructure for better reuse 2024-10-12 22:37:35 +03:00
Eugen Eisler
17bde814cc feat: restructure for better reuse 2024-10-12 22:25:17 +03:00
github-actions[bot]
525f972d22 Update version to v1.4.60 and commit 2024-10-12 15:27:28 +00:00
Eugen Eisler
161ce65ae6 fix: IsChatRequest rule; Close #1042 is 2024-10-12 18:27:04 +03:00
github-actions[bot]
72f1429db9 Update version to v1.4.59 and commit 2024-10-11 22:59:41 +00:00
Daniel Miessler
b5fe3f2ad8 Added ctw to Raycast. 2024-10-11 15:59:26 -07:00
github-actions[bot]
2155ff9fc0 Update version to v1.4.58 and commit 2024-10-11 20:30:50 +00:00
Eugen Eisler
d33175e5b6 chore: we don't need tp configure DryRun vendor 2024-10-11 23:28:56 +03:00
Eugen Eisler
6dbd24e541 fix: Close #1040. Configure vendors separately that were not configured yet 2024-10-11 23:25:33 +03:00
github-actions[bot]
d1c527c421 Update version to v1.4.57 and commit 2024-10-11 19:27:41 +00:00
Eugen Eisler
c0bd61ba49 docs: Close #1035, provide better example for pattern variables 2024-10-11 22:27:20 +03:00
github-actions[bot]
8f0cc85742 Update version to v1.4.56 and commit 2024-10-11 18:54:25 +00:00
Eugen Eisler
7275dfbd6b Merge pull request #1039 from hallelujah-shih/feature/set-default-lang
Feature/set default lang
2024-10-11 20:54:10 +02:00
hallelujah-shih
9f94cfb718 fmt 2024-10-10 19:45:39 +08:00
hallelujah-shih
e1fa674a3f support set default output language
# Conflicts:
#	cli/cli.go
#	core/fabric.go
2024-10-10 19:37:51 +08:00
github-actions[bot]
34f804be3a Update version to v1.4.55 and commit 2024-10-09 09:29:17 +00:00
Eugen Eisler
83cd8a1912 fix: Close #1036 2024-10-09 12:29:02 +03:00
github-actions[bot]
d347ef0dcc Update version to v1.4.54 and commit 2024-10-07 16:44:19 +00:00
Eugen Eisler
44cc9bbcac Merge pull request #1021 from joshuafuller/main
Corrected spelling and grammatical errors for consistency and clarity for transcribe_minutes
2024-10-07 18:44:06 +02:00
github-actions[bot]
4aa0b99c74 Update version to v1.4.53 and commit 2024-10-07 09:17:11 +00:00
Eugen Eisler
d90e8081ac fix: fix NP if response is empty, close #1026, #1027 2024-10-07 11:16:57 +02:00
github-actions[bot]
70d43c5252 Update version to v1.4.52 and commit 2024-10-06 19:50:30 +00:00
Daniel Miessler
fd8216f1bd Merge branch 'main' of github.com:danielmiessler/fabric 2024-10-06 12:50:12 -07:00
Daniel Miessler
ae0c9009d9 Added extract_core_message. 2024-10-06 12:50:05 -07:00
Eugen Eisler
4104317b34 feat: work on Rest API 2024-10-06 16:07:32 +02:00
Eugen Eisler
401f0da689 feat: work on Rest API 2024-10-06 16:06:05 +02:00
Eugen Eisler
6efe7960cd feat: work on Rest API 2024-10-06 15:43:18 +02:00
Eugen Eisler
a6d63f4d0e feat: work on Rest API 2024-10-06 15:40:29 +02:00
Eugen Eisler
8f3928f4b2 feat: work on Rest API 2024-10-06 15:31:06 +02:00
Eugen Eisler
3380972df1 feat: work on Rest API 2024-10-06 15:29:01 +02:00
Joshua Fuller
bad01040e3 Corrected spelling and grammatical errors for consistency and clarity
Description:

Changed "agreed within the meeting" to "agreed upon within the meeting" to improve grammatical accuracy.

Added missing periods to ensure consistency across list items.

Corrected the spelling of "highliting" to "highlighting."

Fixed the spelling of "exxactly" to "exactly."

Updated phrasing in "Write NEXT STEPS a 2-3 sentences" to "Write NEXT STEPS as 2-3 sentences" for grammatical correctness.

These changes improve the readability and consistency of the document, ensuring all instructions are clear and error-free.
2024-10-05 17:29:12 -05:00
github-actions[bot]
0b26b930f9 Update version to v1.4.51 and commit 2024-10-05 16:48:46 +00:00
Eugen Eisler
5dbaf4f28f fix: tests 2024-10-05 18:48:22 +02:00
github-actions[bot]
1d9cce5adf Update version to v1.4.50 and commit 2024-10-05 16:44:19 +00:00
Eugen Eisler
76622105e2 fix: windows release 2024-10-05 18:43:54 +02:00
github-actions[bot]
e6e3e34f53 Update version to v1.4.49 and commit 2024-10-05 16:42:19 +00:00
Eugen Eisler
7192ddbaa6 fix: windows release 2024-10-05 18:41:58 +02:00
github-actions[bot]
b5206d1923 Update version to v1.4.48 and commit 2024-10-05 15:54:01 +00:00
Eugen Eisler
81d60b4292 feat: Add 'meta' role to store meta info to session, like source of input content. 2024-10-05 17:53:48 +02:00
github-actions[bot]
ca6660585d Update version to v1.4.47 and commit 2024-10-05 15:43:21 +00:00
Eugen Eisler
d70e7c570d feat: Add 'meta' role to store meta info to session, like source of input content. 2024-10-05 17:42:57 +02:00
Eugen Eisler
4fb965ec9d feat: Add 'meta' role to store meta info to session, like source of input content. 2024-10-05 17:33:27 +02:00
github-actions[bot]
a5544cb67c Update version to v1.4.46 and commit 2024-10-04 23:05:49 +00:00
Eugen Eisler
679a852c1c feat: Close #1018 2024-10-05 01:05:31 +02:00
Eugen Eisler
c685b4f810 feat: implement print session and context 2024-10-05 01:02:06 +02:00
Eugen Eisler
9452d6bd2a feat: implement print session and context 2024-10-05 00:57:11 +02:00
github-actions[bot]
a6eeff2c91 Update version to v1.4.45 and commit 2024-10-04 20:39:13 +00:00
Eugen Eisler
3eb314320e feat: Setup for specific vendor, e.g. --setup-vendor=OpenAI 2024-10-04 22:38:43 +02:00
github-actions[bot]
14d02073ab Update version to v1.4.44 and commit 2024-10-03 23:55:56 +00:00
Eugen Eisler
17a6020c2d ci: use the latest tag by date 2024-10-04 01:55:36 +02:00
github-actions[bot]
e8d5d75286 Update version to v1.4.43 and commit 2024-10-03 23:51:03 +00:00
Eugen Eisler
eb6430295f ci: use the latest tag by date 2024-10-04 01:50:46 +02:00
github-actions[bot]
c0adf73056 Update version to v1.4.42 and commit 2024-10-03 23:47:32 +00:00
Eugen Eisler
e373c5d5b8 ci: use the latest tag by date 2024-10-04 01:47:16 +02:00
Eugen Eisler
0ed45d34cb ci: use the latest tag by date 2024-10-04 01:44:30 +02:00
github-actions[bot]
6fadd428c0 Update version to v1.4.41 and commit 2024-10-03 23:16:59 +00:00
Eugen Eisler
87d2acbb43 ci: trigger release workflow ony tag_created 2024-10-04 01:16:36 +02:00
github-actions[bot]
3253cd284a Update version to v1.4.40 and commit 2024-10-03 23:09:37 +00:00
Eugen Eisler
bb72fe59d1 ci: create repo dispatch 2024-10-04 01:09:11 +02:00
github-actions[bot]
c88ef3d507 Update version to v1.4.39 and commit 2024-10-03 22:58:52 +00:00
Eugen Eisler
550a06c9ed ci: test tag creation 2024-10-04 00:58:31 +02:00
github-actions[bot]
3b7b9e07e7 Update version to v1.4.38 and commit 2024-10-03 22:54:05 +00:00
Eugen Eisler
bedcc08869 ci: test tag creation 2024-10-04 00:53:33 +02:00
Eugen Eisler
4b6f7ddf27 ci: commit version changes only if it changed 2024-10-04 00:40:46 +02:00
Eugen Eisler
c96a4931d1 ci: commit version changes only if it changed 2024-10-04 00:32:56 +02:00
Eugen Eisler
110eb37ffa ci: use TAG_PAT instead of secrets.GITHUB_TOKEN for tag push 2024-10-04 00:29:00 +02:00
Daniel Miessler
fc3aa9c8d6 Merge branch 'main' of github.com:danielmiessler/fabric 2024-10-02 23:46:46 -07:00
Daniel Miessler
8e2c26d1a0 Updated predictions pattern. 2024-10-02 23:46:37 -07:00
github-actions[bot]
3766bab44a Update version to v1.4.36 and commit 2024-10-03 06:23:00 +00:00
Daniel Miessler
2eed7ec935 Merge branch 'main' of github.com:danielmiessler/fabric 2024-10-02 23:22:42 -07:00
Daniel Miessler
134f54d372 Added redeeming thing. 2024-10-02 23:22:35 -07:00
github-actions[bot]
0ccf34ea8c Update version to v1.4.35 and commit 2024-10-02 22:45:01 +00:00
Eugen Eisler
7e9d7f6f32 feat: clean up html readability; add autm. tag creation 2024-10-03 00:44:20 +02:00
github-actions[bot]
d6d721737b Update version to v1.4.34 and commit 2024-10-02 22:42:32 +00:00
Eugen Eisler
2d102c1a02 feat: clean up html readability; add autm. tag creation 2024-10-03 00:42:14 +02:00
github-actions[bot]
6c185c25f6 Update version to v1.4.33 and commit 2024-10-02 22:26:26 +00:00
Eugen Eisler
b53005a6f1 feat: clean up html readability; add autm. tag creation 2024-10-03 00:26:02 +02:00
Eugen Eisler
e4d34f4937 feat: clean up html readability; add autm. tag creation 2024-10-03 00:23:50 +02:00
Eugen Eisler
689292f7f6 feat: clean up html readability; add autm. tag creation 2024-10-03 00:16:16 +02:00
github-actions[bot]
857b23c3fd Update version to v1.5.0 and commit 2024-10-02 22:12:41 +00:00
Eugen Eisler
42b5cb4413 feat: clean up html readability; add autm. tag creation 2024-10-03 00:12:23 +02:00
github-actions[bot]
3990fe013b Update version to v1.4.32 and commit 81b4bf3 2024-10-02 20:10:21 +00:00
Eugen Eisler
81b4bf3756 Merge pull request #1007 from hallelujah-shih/feature/html-readability
support turn any web page into clean view content
2024-10-02 22:10:02 +02:00
github-actions[bot]
ee1e7d399d Update version to v1.4.32 and commit 859ef04 2024-10-02 18:43:48 +00:00
Daniel Miessler
859ef049d2 Added extract features. 2024-10-02 11:43:29 -07:00
github-actions[bot]
54825ec8d8 Update version to v1.4.32 and commit 99e2e7d 2024-10-02 15:52:15 +00:00
Daniel Miessler
99e2e7da57 Merge pull request #1005 from fn5/patch-1
Update patterns/solve_with_cot/system.md typos
2024-10-02 08:52:00 -07:00
github-actions[bot]
ed34ccfc73 Update version to v1.4.32 and commit c92bcc2 2024-10-02 15:51:40 +00:00
Daniel Miessler
c92bcc24da Merge pull request #962 from alucarded/patch-1
Update prompt in agility_story
2024-10-02 08:51:25 -07:00
Daniel Miessler
6ec0ea4f80 Merge pull request #994 from OddDuck11/main
Add pattern analyze_military_strategy
2024-10-02 08:51:10 -07:00
github-actions[bot]
3c56d23b03 Update version to v1.4.32 and commit bcafea3 2024-10-02 15:50:57 +00:00
Daniel Miessler
bcafea3b98 Merge pull request #1008 from MattBash17/patch-1
Update system.md in transcribe_minutes
2024-10-02 08:50:43 -07:00
github-actions[bot]
33df431993 Update version to v1.4.32 and commit d6551be 2024-10-01 23:09:38 +00:00
Daniel Miessler
d6551bed60 Added primary solution. 2024-10-01 16:09:20 -07:00
github-actions[bot]
fe620ed278 Update version to v1.4.32 and commit 7b8ea76 2024-10-01 20:04:52 +00:00
Eugen Eisler
7b8ea76239 ci: update version 2024-10-01 22:04:15 +02:00
github-actions[bot]
9a55514bc0 Update version to v1.4.31 and commit 5012621 2024-10-01 19:59:18 +00:00
Daniel Miessler
5012621721 Merge pull request #987 from joshmedeski/better-ls
feat: remove cli list label and indentation
2024-10-01 12:59:04 -07:00
github-actions[bot]
9e676fdcd8 Update version to v1.4.31 and commit e048980 2024-10-01 19:29:27 +00:00
Daniel Miessler
e0489803f4 Merge branch 'main' of github.com:danielmiessler/fabric 2024-10-01 12:29:10 -07:00
Daniel Miessler
6c927e23b5 Added epp. 2024-10-01 12:29:03 -07:00
github-actions[bot]
8fb7f19fb1 Update version to v1.4.31 and commit a0ba207 2024-10-01 19:27:02 +00:00
Daniel Miessler
a0ba207db9 Merge branch 'main' of github.com:danielmiessler/fabric 2024-10-01 12:26:42 -07:00
Daniel Miessler
58f8d21ec0 Added create_story_explanation. 2024-10-01 12:26:35 -07:00
github-actions[bot]
2b87109e52 Update version to v1.4.31 and commit f54a052 2024-10-01 19:25:00 +00:00
Daniel Miessler
f54a052533 Merge branch 'main' of github.com:danielmiessler/fabric 2024-10-01 12:24:34 -07:00
Daniel Miessler
a67dd0555a Added create_story_explanation. 2024-10-01 12:24:26 -07:00
github-actions[bot]
19b568b075 Update version to v1.4.31 and commit 5d3e0da 2024-10-01 15:10:44 +00:00
Eugen Eisler
5d3e0dae49 Merge pull request #1011 from fooman/grab-language-specific-transcription-from-youtube
Grab transcript from youtube matching the user's language
2024-10-01 17:10:29 +02:00
Kristof Ringleff, Fooman
be37d889a0 Grab transcript from youtube matching the user's language instead of the first one 2024-10-01 14:51:32 +13:00
github-actions[bot]
be9046aee9 Update version to v1.4.31 and commit 7e63a16 2024-09-30 16:25:11 +00:00
Eugen Eisler
7e63a16f28 feat: add version updater bot 2024-09-30 18:24:53 +02:00
MattBash17
98292df3cc Update system.md in transcribe_minutes 2024-09-30 16:39:55 +02:00
hallelujah-shih
cc8711cc07 support turn any web page into clean view content 2024-09-30 11:25:10 +08:00
github-actions[bot]
211c0fece0 Update version to v1.4.30 and commit 7d6cb56 2024-09-29 20:17:10 +00:00
Eugen Eisler
7d6cb5604f feat: add version updater bot 2024-09-29 22:16:54 +02:00
github-actions[bot]
f774c95865 Update version to v1.4.29 and commit 8445b6a 2024-09-29 19:58:32 +00:00
Eugen Eisler
8445b6aad7 feat: add version updater bot 2024-09-29 21:58:14 +02:00
Eugen Eisler
2f881a2c06 feat: add version updater bot 2024-09-29 21:51:49 +02:00
Eugen Eisler
6fc4d91a29 feat: add version updater bot 2024-09-29 21:46:49 +02:00
Eugen Eisler
6126a14c9f feat: add version updater bot 2024-09-29 21:42:24 +02:00
Eugen Eisler
9aa2e00b16 feat: add version updater bot 2024-09-29 21:36:43 +02:00
Eugen Eisler
3a7e1cf527 feat: add version updater bot 2024-09-29 21:35:20 +02:00
Eugen Eisler
913210d2a9 chore: supress printing YouTube transcripts/comments and grabed Web Sites if a pattern is defined 2024-09-29 19:33:09 +02:00
Jack
b2a2ea0c03 Update patterns/solve_with_cot/system.md part three
Noticed the opening closing brackets were incorrect < >
2024-09-29 17:41:14 +10:00
Jack
ad6260fab5 Update patterns/solve_with_cot/system.md typos part two
Forgot the last typos. Sorry.
2024-09-29 16:46:47 +10:00
Jack
522c1038fb Update patterns/solve_with_cot/system.md typos 2024-09-29 16:38:40 +10:00
Eugen Eisler
cab365f496 docs: update YouTube example call 2024-09-27 12:26:07 +02:00
Eugen Eisler
e714b6c5bf fix: skip cli test for now 2024-09-27 12:12:38 +02:00
Eugen Eisler
d8c3c29ff8 fix: cli test 2024-09-27 12:07:11 +02:00
Eugen Eisler
273ba3e943 feat: extend installation instruction to get the latest release binaries 2024-09-27 11:51:43 +02:00
Eugen Eisler
70c5aacb45 feat: extend installation instruction to get the latest release binaries 2024-09-27 11:50:47 +02:00
Eugen Eisler
c5db39a06d feat: extend installation instruction to get the latest release binaries 2024-09-27 11:50:47 +02:00
Eugen Eisler
fa457e7812 Merge pull request #996 from hallelujah-shih/feature/wipe_parameter
add wipe flag for ctx and session
2024-09-27 11:50:29 +02:00
shih
dd6cd06b5a add wipe flag for ctx and session 2024-09-27 07:43:08 +08:00
Eugen Eisler
41b711f1ca doc: update flags order 2024-09-26 21:40:53 +02:00
Eugen Eisler
8f7acac2b1 fix: #986 implement --version flag 2024-09-26 21:09:01 +02:00
Eugen Eisler
ea323c12ff fix: #986 implement --version flag 2024-09-26 20:57:37 +02:00
Eugen Eisler
dd83d7faca fix: #997 use setting env value over default values 2024-09-26 19:37:36 +02:00
Eugen Eisler
28937bb8ca Merge pull request #967 from akashkankariya/patch-1
Updated Path to install to_pdf in readme[Bug Fix]
2024-09-25 23:59:03 +02:00
Eugen Eisler
a1c81c41cb Merge pull request #984 from riccardo1980/feature/seed_parameter
adding flag for pinning seed in openai and compatible APIs
2024-09-25 23:52:18 +02:00
OddDuck11
24816f25d1 Add pattern analyze_military_strategy
Use this pattern to analyze real historic, or fictional battle strategy.
2024-09-25 14:31:07 -05:00
Daniel Miessler
fcddedfe72 Merge branch 'main' of github.com:danielmiessler/fabric 2024-09-24 13:39:37 -07:00
Daniel Miessler
77c7323a39 Added a generic TELOS file, Alma.md to the repo. 2024-09-24 13:39:33 -07:00
Riccardo Zanella
7322d249e2 Merge branch 'main' into feature/seed_parameter 2024-09-24 10:50:29 +02:00
Riccardo Zanella
cfae9efcbb updating README with the new flag 2024-09-24 10:45:58 +02:00
Eugen Eisler
70aac4e5f9 Merge pull request #991 from aculich/fix-apple-silicon-goroot-path
Fix GOROOT path for Apple Silicon Macs
2024-09-23 21:35:35 +02:00
Aaron Culich
4a34355c3b Fix GOROOT path for Apple Silicon Macs in setup instructions
The previous instructions incorrectly set GOROOT to '/opt/homebrew/bin/go', which points to the Go binary rather than the Go root directory. This caused errors when running Go commands on Apple Silicon-based Macs.

I updated the instructions to dynamically determine the correct GOROOT path using Homebrew, ensuring compatibility across different environments. This change resolves the 'go: cannot find GOROOT directory' issue on M1/M2 Macs.
2024-09-22 20:37:52 -07:00
Josh Medeski
c51c4c8d3e feat: remove cli list label and indentation 2024-09-21 06:49:14 -05:00
Riccardo Zanella
a619c915e1 adding flag for pinning seed in openai and compatible APIs 2024-09-20 17:59:35 +02:00
Eugen Eisler
f4044cde7e feat: #979 add support for configurable base url for Anthropic 2024-09-20 13:16:49 +02:00
Eugen Eisler
a31af9fa80 Merge pull request #976 from pavdmyt/pavdmyt/fix-changeDefaultModel-flag-descr
fix: correct changeDefaultModel flag description
2024-09-20 12:56:39 +02:00
Eugen Eisler
a61590efeb Merge pull request #957 from quarechen/specify_language_return
Add cmd -g to select the language would reply
2024-09-19 22:42:29 +02:00
Eugen Eisler
26fccfe18e feat: integrate the output language to the system/user prompt 2024-09-19 22:41:05 +02:00
Eugen Eisler
2b79a058de Merge branch 'main' into specify_language_return 2024-09-19 22:31:51 +02:00
Eugen Eisler
64da74e25a chore: update dependencies 2024-09-18 14:38:37 +02:00
Eugen Eisler
23cb9a9ee8 chore: #975 check choices available 2024-09-18 14:19:00 +02:00
Eugen Eisler
c2a15f6aa1 chore: #975 check choices available 2024-09-18 14:08:09 +02:00
Pavel Dmytrenko
c293c6137b fix: correct changeDefaultModel flag description 2024-09-18 11:58:33 +03:00
Eugen Eisler
d2e2a6537e feat: improve Jina AI impl. 2024-09-16 22:06:32 +02:00
Eugen Eisler
a6fc13dbdc Merge pull request #861 from noamsiegel/scrape_url
Scrape url
2024-09-16 21:37:10 +02:00
Noam Siegel
4534ef6544 made jina api key optional 2024-09-16 11:44:21 -07:00
Eugen Eisler
ebc0239339 Merge pull request #970 from mark-kazakov/mistral-vendor
add mistral vendor
2024-09-16 17:34:49 +02:00
Mark Kazakov
bd33795f72 add mistral vendor 2024-09-16 09:28:00 +03:00
Daniel Miessler
39aade44f6 Updated readme. 2024-09-15 19:47:43 -07:00
Daniel Miessler
3ae503c969 Updated readme. 2024-09-15 19:39:22 -07:00
Daniel Miessler
780fc42aa0 Updated readme. 2024-09-15 19:38:43 -07:00
Daniel Miessler
2967cfd1d4 Updated readme. 2024-09-15 19:38:09 -07:00
Daniel Miessler
0b28847e5d Merge branch 'main' of github.com:danielmiessler/fabric 2024-09-15 19:31:53 -07:00
Daniel Miessler
3a62c12791 Updated readme. 2024-09-15 19:31:45 -07:00
Eugen Eisler
75c1e84e08 feat: use -r, --raw: Use defaults of model (don't send temperature etc.) and use the user role instead of the system role. 2024-09-15 20:42:25 +02:00
Eugen Eisler
147da29c1a feat: use -r, --raw: Use defaults of model (don't send temperature etc.) and use the user role instead of the system role. 2024-09-15 20:38:19 +02:00
Eugen Eisler
329c843567 feat: implement -u, --user-instead-of-system: Use the user role instead of the system role for the pattern. It is needed for Open AI o1 models for now. 2024-09-15 15:09:45 +02:00
Akash Kankariya
3bf1c95dc2 Updated Path to install to_pdf [Bug Fix] 2024-09-15 12:55:12 +05:30
Tomasz Edward Posluszny
0bc7cd9785 Update system.md
Topic should make sense
2024-09-14 12:45:12 +02:00
csquarechen
680129e370 update the discription of language commend 2024-09-14 11:30:55 +08:00
csquarechen
d574670084 use default language param default:"" to avoid changes in the output. 2024-09-14 10:49:55 +08:00
Arpit Pathak
5ad9943462 Merge branch 'main' into patch-1 2024-09-13 23:19:19 +05:30
csquarechen
b96415d445 feat: add cmd -g to select the language would reply 2024-09-13 16:41:54 +08:00
Noam Siegel
6f116ca527 feat: Add Jina AI integration for web scraping and question search 2024-09-09 21:22:19 -07:00
Noam Siegel
bac7d87390 Merge branch 'main' into scrape_url 2024-09-09 09:47:19 -07:00
Arpit Pathak
cf0b9d2c3d Create setup_fabric.bat, a batch script to automate setup and running fabric on windows. 2024-08-22 09:02:30 +05:30
Noam Siegel
b2be94f2f8 added back some debug statements 2024-08-21 11:03:04 -07:00
Noam Siegel
9b4c20dd19 removed debug statements 2024-08-21 10:54:57 -07:00
Noam Siegel
c7449c68b7 chore: Add ScrapeURL flag for CLI to scrape website URL to markdown using Jina AI 2024-08-21 10:44:57 -07:00
87 changed files with 2953 additions and 1407 deletions

View File

@@ -1,8 +1,9 @@
name: Go Release
on:
repository_dispatch:
types: [ tag_created ]
push:
branches: ["main"]
tags:
- "v*"
@@ -15,6 +16,8 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
@@ -40,6 +43,8 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
@@ -88,23 +93,36 @@ jobs:
name: fabric-windows-${{ matrix.arch }}.exe
path: fabric-windows-${{ matrix.arch }}.exe
- name: Get latest tag
if: matrix.os != 'windows-latest'
id: get_latest_tag
run: |
latest_tag=$(git tag --sort=-creatordate | head -n 1)
echo "latest_tag=$latest_tag" >> $GITHUB_ENV
- name: Get latest tag
if: matrix.os == 'windows-latest'
id: get_latest_tag_windows
run: |
$latest_tag = git tag --sort=-creatordate | Select-Object -First 1
Add-Content -Path $env:GITHUB_ENV -Value "latest_tag=$latest_tag"
- name: Create release if it doesn't exist
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release view ${{ github.ref_name }} || gh release create ${{ github.ref_name }} --title "Release ${{ github.ref_name }}" --notes "Automated release for ${{ github.ref_name }}"
gh release view ${{ env.latest_tag }} || gh release create ${{ env.latest_tag }} --title "Release ${{ env.latest_tag }}" --notes "Automated release for ${{ env.latest_tag }}"
- name: Upload release artifact
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
if: matrix.os == 'windows-latest'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload ${{ github.ref_name }} fabric-windows-${{ matrix.arch }}.exe
gh release upload ${{ env.latest_tag }} fabric-windows-${{ matrix.arch }}.exe
- name: Upload release artifact
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && matrix.os != 'windows-latest'
if: matrix.os != 'windows-latest'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload ${{ github.ref_name }} fabric-${OS}-${{ matrix.arch }}
gh release upload ${{ env.latest_tag }} fabric-${OS}-${{ matrix.arch }}

View File

@@ -0,0 +1,81 @@
name: Update Version File and Create Tag
on:
push:
branches:
- main # Monitor the main branch
permissions:
contents: write # Ensure the workflow has write permissions
jobs:
update-version:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Get the latest tag
id: get_latest_tag
run: |
latest_tag=$(git tag --sort=-creatordate | head -n 1)
echo "Latest tag is: $latest_tag"
echo "tag=$latest_tag" >> $GITHUB_ENV # Save the latest tag to environment file
- name: Increment patch version
id: increment_version
run: |
latest_tag=${{ env.tag }}
major=$(echo "$latest_tag" | cut -d. -f1 | sed 's/v//')
minor=$(echo "$latest_tag" | cut -d. -f2)
patch=$(echo "$latest_tag" | cut -d. -f3)
new_patch=$((patch + 1))
new_tag="v${major}.${minor}.${new_patch}"
echo "New tag is: $new_tag"
echo "new_tag=$new_tag" >> $GITHUB_ENV # Save the new tag to environment file
- name: Update version.go file
run: |
echo "package main" > version.go
echo "" >> version.go
echo "var version = \"${{ env.new_tag }}\"" >> version.go
- name: Commit changes
run: |
git add version.go
if ! git diff --staged --quiet; then
git commit -m "Update version to ${{ env.new_tag }} and commit $commit_hash"
else
echo "No changes to commit."
fi
- name: Push changes
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Use GITHUB_TOKEN to authenticate the push
run: |
git push origin main # Push changes to the main branch
- name: Create a new tag
env:
GITHUB_TOKEN: ${{ secrets.TAG_PAT }}
run: |
git tag ${{ env.new_tag }}
git push origin ${{ env.new_tag }} # Push the new tag
- name: Dispatch event to trigger release workflow
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Use GITHUB_TOKEN to authenticate the dispatch
run: |
curl -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${{ github.repository }}/dispatches \
-d '{"event_type": "tag_created", "client_payload": {"tag": "${{ env.new_tag }}"}}'

1
.gitignore vendored
View File

@@ -11,6 +11,7 @@ __pycache__/
# Distribution / packaging
.Python
.idea
build/
develop-eggs/
dist/

200
Alma.md Normal file
View File

@@ -0,0 +1,200 @@
## Document Purpose
This document captures the SPQA policy and State for Alma Security, a security startup out of Redwood City, Ca.
This is part of the SPQA context that will be used to answer questions and create artifacts for the company, e.g., company strategy, security strategy, quarterly security reports (QSRs), project plans, recommendations on which projects to undertake, which investments to take and avoid, and other such decisions.
A major aspect of the SPQA system is the definition of the company's mission, goals, KPIs, and challenges. These shape everything within the company and thus should be used to shape the recommendations made when asked.
In addition to the clearly stated goals and other defining characteristics listed above, there will also be a streaming list of updates coming into this system using the Activity document.
Those will be changes, updates, or modifications to the direction of the company. For example, if Goal number 4 is to build a new datacenter in Boise, Idaho, but we see an update in the Activity section that says we've lost the ability to build in Boise, we should consider goal #4 out of the picture for prioritization and other decision purposes. In other words, the streaming activity log into this document should be considered updates to the core content.
## Company History
Alma Security was started by Chris Meyers, who was previously at Sigma Systems as CTO and HPE as a senior security engineer.
He started the company becuase, "I saw a gap in the authentication market, where companies were only looking at one or two aspects of one's identity to do authentication. They we're looking at the whole picture and turning that into a continuous authentication story."
## Company Mission
The mission of Alma Security is to ensure businesses can continuously authenticate their users using their whole selves.
## Company Goals (G1 means goal 1, G2 is goal 2, etc. Treat each item (goal/kpi/etc) as half as important as the one before it.)
NOTE: Some goals are things like project rollouts which serve the higher goals. In that case they shouldn't always be considered so much lower priority because one is serving the other.
## Company Goals
- G1: Achieve 20% market share by January 2025
- G2: Hit 10000 active customers by January 2025
- G3: Hit a customer trust score of 90+% by January 2025
- G4: Get churn below 5% by August 2024
- G5: Launch in Europe by August 2024
- G6: Launch in India by November 2024
- G7: Launch Mood-monitor integration by February 2024
- G8: Launch partnership with Apple Passkeys by June 2024
## Company KPIs
- K1: Current marketshare percentage
- K2: Number of active customers
- K3: Current churn percentage
- K4: Launched_in_Europe (yes/no)
- K4: Launched_in_India (yes/no)
-----------------------------------------------------------------------------------------------------------------------
## Security Team Mission
- SM1: Protect Alma Security's customers and intellectual property from security and privacy incidents.
## Security Team Goals
- SG1: Secure all customer data -- especially biometric -- from security and privacy incidents.
- SG2: Protect Alma Security's intellectual property from being captured by unathorized parties.
- SG3: Reach a time to detect malicious behavior of less than 4 minutes by January 2025
- SG4: Ensure the public trusts our product, because it's an authentication product we can't survive if people don't trust us.
- SG5: Reach a time to remediate critical vulnerabilties on crown jewel systems of less than 16 hours by August 2025
- SG6: Reach a time to remediate critical vulnerabilties on all systems of less than 3 days by August 2025
- SG7: Complete audit of Apple Passkey integration by February 2025
- SG8: Complete remediation of Apple Passkey vulns by February 2025
## Security Team KPIs (How we measure the team)
- SK1: TTD: Time to detect malicious behavior (Minutes)
- SK1: TTI: Time to begin investigation of malicious behavior (Minutes)
- SK3: TTR-CJC: Time to remediate critical vulnerabilities on crown jewel systems (Hours)
- SK3: TTR-C: Time to remediate critical vulnerabilities on all systems (Hours)
- SK4: PT: Public trust score (Complete, Significant, Moderate, Minimal, Distrust, N/A)
## Risk Register (The things we're most worried about)
- R1: Our infrastructure security team is understaffed by 50% after 5 key people left
- R2: We are not currently monitoring our external perimeter for attack surface related vulnerabilities like open ports, listening applications, unknown hosts, unknown subdomains pointing to these things, etc. We only do scans once every couple of months and we don't really have anyone to look at the results
- R3: It takes us multiple days to investigate potential malicious behavior on our systems.
- R4: We lack a full list of our assets, including externally facing hosts, S3 buckets, etc., which make up our attack surface
- R5: We have a low public trust score due to the events of 2022.
## Security Team Narrative
### Background
Alma hired a new security team starting in January of 2023 and we have been building out the program since then. The philosophy and approach for the security team is to explicitly articulate what we believe the highest risks are to Alma, to deploy targeted strategies to address those risks, and to use clear, transparent KPIs to show progress towards our goals over time.
### Current Risks
So our risk register looks like this:
1. We are understaffed by 50% after 5 key people left in 2022
2. Our perimeter is not being monitored for attack surface related vulnerabilities
3. It takes us too long to detect and start investigating malicious behavior on our systems
4. We do not have a full list of our assets, which makes it difficult to know what we need to protect
5. We have a low public trust score due to the events of 2022
### Strategies
As such, our strategies are as follows:
1. Hire 5 more A-tier security professionals
2. Purchase and implement an attack surface management solution
3. Invest in our detection and response capabilities
4. Purchase an asset inventory system that integrates with our attack surface management tool
5. Leverage PR to share as much of our progress as possible with the public to rebuild trust
### How We're Doing
We believe being transparent about our progress is key to everything, and for that reason we maintain a limited number of KPIs that we update every quarter. These metrics will not change often. They will remain consistent so that it's easy to track how we're spending our resources and the progress we're making.
Those KPIs are:
1. Time to detect malicious behavior
2. Time to start investigating malicious behavior
3. Time to remediate critical vulnerabilities on crown jewel systems
4. Time to remediate critical vulnerabilities on all systems
5. Our public trust score
As of $DATE$, our KPIs for these are currently:
$GIVE CURRENT KPIs from the Activity section below$
$INSERT GRAPHS OF KPI PROGRESS OVER TIME HERE$
## Security Team Strategies
- STS1: Hire 5 more A-tier security professionals
- STS2: Purchase an attack surface management solution
- STS3: Invest in our detection and response capabilities
- STS4: Purchase an asset inventory system that integrates with our attack surface management tool
## Infrastructure Notes (a basic description of our tech stack and various context around it)
- We currenty have no WAF protecting our main web app but we're considering adding one
- We have had some issues with S3 buckets becoming public, or being set up as public, which has lead to some close calls with customer data almost being exposed.
- alma.amazon-domain.com is our primary S3 bucket that contains everything, but it's not public readable or listable
- We have a root account for our AWS account that doesn't yet have 2FA on it, but we're working on fixing that within a few weeks (but it's been open for a few months)
- We also use Postgres for all our databases.
- Developers have root access to the all kubernetes nodes via SSH on port 45,001 using a shared developer key issued during laptop provisioning.
- We're a kubernetes shop and do everything through AWS
- We're logging most stuff to Cloudtrail and we kind of use guarduty, but we don't have a 24/7 team to monitor alerts and logs. We should add that to our list of challenges next time we update our overarll policy
- We also have a Windows infrastructure because some key personell came from Microsoft. The DC is hosted in our head office which is in Redwood City, and anyone who works in that office (most of the 300 employees) uses that to log in when tehy start work. The domain is ALMA.
- There's a domain-joined fileserver running Windows 2012 that most people use to upload new ideas and plans for new products. It uses Windows authentication from the domain.
- We use a palo alto firewall with 2fa using windows authenticator tied to SSO.
- The name of the AI system doing all this context creation using SPQA is Alma, which is also the name of the company.
- We use Workday for HR stuff. Slack for realtime communications. Outlook 365 as a service. Sentinel One on the workstations and laptops. Servers in AWS are mostly Amazon Linux 2 with a few Ubuntu boxes that are a few years old.
- We also primarily use Postgres for all of our systems.
## Team
TEAM MEMBER | TEAM ASSIGNED | SKILLS | PAY LEVEL | LOCATION | PROJECTS
Nadia Khan | Detection and Response | D&R (Expert), AWS (Strong), Python (Expert), Kubernetes (Basic), Postgres (Basic) | $249K | Redwood City
Chris Magann | Vulnerability Management | VM (Expert), AWS (Strong), Python (Basic), Postgres (Basic) | $212K | Redwood City
Tigan Wang | Vulnerability Management | VM (Expert), AWS (Strong), Python (Basic), Postgres (Basic) | $217K | Redwood City
## Projects
PROJECT NAME | PROJECT DESCRIPTION | PROJECT PRIORITY | PROJECT MEMBERS | START DATE | END DATE | STATUS | PROJECT COST
WAF Install | Install a WAF in front of our main web app | Critical | Nadia Khan | 2024-01-01 - Ongoing | In Progress | $112K one-time, $9K/month
Multi-Factor Authentication (MFA) Rollout | Implement MFA across all internal and external systems | Critical | Chris Magaan | 2024-01-15 | 2024-05-01 | Planned | $80K one-time, $5K/month
Procure and Implement ASM | Implement continuous monitoring for attack surface vulnerabilities | High | Tigan Wang | 2024-02-15 | 2024-06-15 | Not Started | $75K one-time, $6K/month
Data Encryption Upgrade | Upgrade encryption protocols for all sensitive data | Medium | Nadia Khan | 2024-04-01 | 2024-08-01 | Planned | $95K one-time
Incident Response Enhancement | Develop and implement a 24/7 incident response team | High | Nadia Khan | 2024-03-01 | 2024-07-01 | In Progress | $150K one-time, $10K/month
Cloud Security Optimization | Optimize AWS cloud security configurations and practices | Medium | Tigan Wang | 2024-02-01 | 2024-06-01 | In Progress | $100K one-time, $8K/month
S3 Bucket Security | Review and secure all S3 buckets to prevent data breaches | High | Chris Magaan | 2024-01-10 | 2024-04-10 | In Progress | $70K one-time, $5K/month
SQL Injection Mitigation | Implement measures to eliminate SQL injection vulnerabilities | High | Tigan Wang | 2024-01-20 | 2024-05-20 | Not Started | $60K one-time
## CURRENT STATE (KPIs, Metrics, Project Activity Updates, etc.)
- October 2022: Current time to detect malicious behavior is 81 hours
- October 2022: Current time to start investigating malicious behavior is 82 hours
- October 2022: Current time to remediate critical vulnerabilities on crown jewel systems is 21 days
- October 2022: Current time to remediate critical vulnerabilities on all systems is 51 days
- January 2023: Current time to detect malicious behavior is 62 hours
- January 2023: Current time to start investigating malicious behavior is 72 hours
- January 2023: Current time to remediate critical vulnerabilities on crown jewel systems is 17 days
- January 2023: Current time to remediate critical vulnerabilities on all systems is 43 days
- July 2023: Current time to detect malicious behavior is 29 hours
- July 2023: Current time to start investigating malicious behavior is 41 hours
- July 2023: Current time to remediate critical vulnerabilities on crown jewel systems is 12 days
- July 2023: Current time to remediate critical vulnerabilities on all systems is 29 days
- November 2023: Current time to start detect malicious behavior is 12 hours
- November 2023: Current time to start investigating malicious behavior is 16 hours
- November 2023: Current time to remediate critical vulnerabilities on crown jewel systems is 9 days
- November 2023: Current time to remediate critical vulnerabilities on all systems is 17 days
- February 2024: Started attack surface management vendor selection process
- January 2024: Current time to start detect malicious behavior is 9 hours
- January 2024: Current time to start investigating malicious behavior is 14 hours
- January 2024: Current time to remediate critical vulnerabilities on crown jewel systems is 8 days
- January 2024: Current time to remediate critical vulnerabilities on all systems is 12 days
- March 2024: We're now remediating crits on crown jewels in less than 6 days
- April 2024: We're now remediating all criticals within 11 days
- July 2024: Criticals are now being fixed in 9 days
- On August 5 we got remediation of critical vulnerabilities down to 7 days

View File

@@ -2,7 +2,7 @@
- The goal is to bring more encapsulation of the models management and simplified configuration management to bring increased flexibility, transparency on the overall flow, and simplicity in adding new model.
- We need to differentiate:
- Vendors: the producer of models (like OpenAI, Anthropric, Ollama, ..etc) and their associated APIs
- Vendors: the producer of models (like OpenAI, Azure, Anthropric, Ollama, ..etc) and their associated APIs
- Models: the LLM models these vendors are making public
- Each vendor and operations allowed by the vendor needs to be encapsulated. This includes:
- The questions needed to setup the model (like the API key, or the URL)

136
README.md
View File

@@ -14,6 +14,7 @@
<h4><code>fabric</code> is an open-source framework for augmenting humans using AI.</h4>
</p>
[Updates](#updates) •
[What and Why](#whatandwhy) •
[Philosophy](#philosophy) •
[Installation](#Installation) •
@@ -28,6 +29,7 @@
## Navigation
- [Updates](#updates)
- [What and Why](#what-and-why)
- [Philosophy](#philosophy)
- [Breaking problems into components](#breaking-problems-into-components)
@@ -46,16 +48,18 @@
<br />
## Updates
> [!NOTE]
August 20, 2024 — We have migrated to Go, and the transition has been pretty smooth! The biggest thing to know is that **the previous installation instructions in the various Fabric videos out there will no longer work** because they were for the legacy (Python) version. Check the new [install instructions](#Installation) below.
>
>
> **The following command line options were changed during the migration to Go:**
September 15, 2024 — Lots of new stuff!
> * Fabric now supports calling the new `o1-preview` model using the `-r` switch (which stands for raw. Normal queries won't work with `o1-preview` because they disabled System access and don't allow us to set `Temperature`.
> * We have early support for Raycast! Under the `/patterns` directory there's a `raycast` directory with scripts that can be called from Raycast. If you add a scripts directory within Raycast and point it to your `~/.config/fabric/patterns/raycast` directory, you'll then be able to 1) invoke Raycast, type the name of the script, and then 2) paste in the content to be passed, and the results will return in Raycast. There's currently only one script in there but I am (Daniel) adding more.
> * **Go Migration: The following command line options were changed during the migration to Go:**
> * You now need to use the -c option instead of -C to copy the result to the clipboard.
> * You now need to use the -s option instead of -S to stream results in realtime.
> * The following command line options have been removed --agents (-a), --gui, --clearsession, --remoteOllamaServer, and --sessionlog options
> * You can now use --Setup (-S) to configure an Ollama server.
> * **Please be patient while our developers rewrite the gui in go**
> * The following command line options have been removed `--agents` (-a), `--gui`, `--clearsession`, `--remoteOllamaServer`, and `--sessionlog`
> * You can now use (-S) to configure an Ollama server.
> * **We're working on a GUI rewrite in Go as well**
## Intro videos
@@ -109,6 +113,29 @@ Fabric has Patterns for all sorts of life and work activities, including:
## Installation
To install Fabric, you can use the latest release binaries or install it from the source.
### Get Latest Release Binaries
```bash
# Windows:
curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-windows-amd64.exe > fabric.exe && fabric.exe --version
# MacOS (arm64):
curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-darwin-arm64 > fabric && chmod +x fabric && ./fabric --version
# MacOS (amd64):
curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-darwin-amd64 > fabric && chmod +x fabric && ./fabric --version
# Linux (amd64):
curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-linux-amd64 > fabric && chmod +x fabric && ./fabric --version
# Linux (arm64):
curl -L https://github.com/danielmiessler/fabric/releases/latest/download/fabric-linux-arm64 > fabric && chmod +x fabric && ./fabric --version
```
### From Source
To install Fabric, [make sure Go is installed](https://go.dev/doc/install), and then run the following command.
```bash
@@ -133,9 +160,9 @@ export PATH=$GOPATH/bin:$GOROOT/bin:$HOME/.local/bin:$PATH
for Apple Silicon based macs
```bash
# Golang environment variables
export GOROOT=/opt/homebrew/bin/go
export GOROOT=$(brew --prefix go)/libexec
export GOPATH=$HOME/go
export PATH=$GOPATH/bin:$GOROOT/bin:$HOME/.local/bin:$PATH:
export PATH=$GOPATH/bin:$GOROOT/bin:$HOME/.local/bin:$PATH
```
### Setup
@@ -169,7 +196,7 @@ Then [set your environmental variables](#environmental-variables) as shown above
The great thing about Go is that it's super easy to upgrade. Just run the same command you used to install it in the first place and you'll always get the latest version.
```bash
go install github.com/danielmiessler/fabric@latest
go install -ldflags "-X main.version=$(git describe --tags --always)" github.com/danielmiessler/fabric@latest
```
## Usage
@@ -180,39 +207,50 @@ fabric -h
```
```bash
usage: fabric -h
Usage:
fabric [OPTIONS]
Application Options:
-p, --pattern= Choose a pattern
-v, --variable= Values for pattern variables, e.g. -v=$name:John -v=$age:30
-C, --context= Choose a context
--session= Choose a session
-S, --setup Run setup
--setup-skip-update-patterns Skip update patterns at setup
-t, --temperature= Set temperature (default: 0.7)
-T, --topp= Set top P (default: 0.9)
-s, --stream Stream
-P, --presencepenalty= Set presence penalty (default: 0.0)
-F, --frequencypenalty= Set frequency penalty (default: 0.0)
-l, --listpatterns List all patterns
-L, --listmodels List all available models
-x, --listcontexts List all contexts
-X, --listsessions List all sessions
-U, --updatepatterns Update patterns
-c, --copy Copy to clipboard
-m, --model= Choose model
-o, --output= Output to file
-n, --latest= Number of latest patterns to list (default: 0)
-d, --changeDefaultModel Change default pattern
-y, --youtube= YouTube video url to grab transcript, comments from it and send to chat
--transcript Grab transcript from YouTube video and send to chat
--comments Grab comments from YouTube video and send to chat
--dry-run Show what would be sent to the model without actually sending it
-p, --pattern= Choose a pattern from the available patterns
-v, --variable= Values for pattern variables, e.g. -v=#role:expert -v=#points:30"
-C, --context= Choose a context from the available contexts
--session= Choose a session from the available sessions
-S, --setup Run setup for all reconfigurable parts of fabric
-t, --temperature= Set temperature (default: 0.7)
-T, --topp= Set top P (default: 0.9)
-s, --stream Stream
-P, --presencepenalty= Set presence penalty (default: 0.0)
-r, --raw Use the defaults of the model without sending chat options (like temperature etc.) and use the user role instead of the system role for patterns.
-F, --frequencypenalty= Set frequency penalty (default: 0.0)
-l, --listpatterns List all patterns
-L, --listmodels List all available models
-x, --listcontexts List all contexts
-X, --listsessions List all sessions
-U, --updatepatterns Update patterns
-c, --copy Copy to clipboard
-m, --model= Choose model
-o, --output= Output to file
--output-session Output the entire session (also a temporary one) to the output file
-n, --latest= Number of latest patterns to list (default: 0)
-d, --changeDefaultModel Change default model
-y, --youtube= YouTube video "URL" to grab transcript, comments from it and send to chat
--transcript Grab transcript from YouTube video and send to chat (it used per default).
--comments Grab comments from YouTube video and send to chat
-g, --language= Specify the Language Code for the chat, e.g. -g=en -g=zh
-u, --scrape_url= Scrape website URL to markdown using Jina AI
-q, --scrape_question= Search question using Jina AI
-e, --seed= Seed to be used for LMM generation
-w, --wipecontext= Wipe context
-W, --wipesession= Wipe session
--printcontext= Print context
--printsession= Print session
--readability Convert HTML input into a clean, readable view
--dry-run Show what would be sent to the model without actually sending it
--version Print current version
Help Options:
-h, --help Show this help message
-h, --help Show this help message
```
@@ -253,7 +291,7 @@ pbpaste | fabric --stream --pattern analyze_claims
3. Run the `extract_wisdom` Pattern with the `--stream` option to get immediate and streaming results from any Youtube video (much like in the original introduction video).
```bash
yt --transcript https://youtube.com/watch?v=uXs-zPc63kM | fabric --stream --pattern extract_wisdom
fabric -y "https://youtube.com/watch?v=uXs-zPc63kM" --stream --pattern extract_wisdom
```
4. Create patterns- you must create a .md file with the pattern and save it to ~/.config/fabric/patterns/[yourpatternname].
@@ -294,26 +332,6 @@ This feature works with all openai and ollama models but does NOT work with clau
Fabric also makes use of some core helper apps (tools) to make it easier to integrate with your various workflows. Here are some examples:
`yt` is a helper command that extracts the transcript from a YouTube video. You can use it like this:
```bash
yt https://www.youtube.com/watch?v=lQVcbY52_gY
```
This will return the transcript from the video, which you can then pipe into Fabric like this:
```bash
yt https://www.youtube.com/watch?v=lQVcbY52_gY | fabric --pattern extract_wisdom
```
### `yt` Installation
To install `yt`, install it the same way as you install Fabric, just with a different repo name.
```bash
go install github.com/danielmiessler/yt@latest
```
Be sure to add your `YOUTUBE_API_KEY` to `~/.config/fabric/.env`.
### `to_pdf`
`to_pdf` is a helper command that converts LaTeX files to PDF format. You can use it like this:
@@ -337,7 +355,7 @@ This will create a PDF file named `output.pdf` in the current directory.
To install `to_pdf`, install it the same way as you install Fabric, just with a different repo name.
```bash
go install github.com/danielmiessler/fabric/to_pdf/to_pdf@latest
go install github.com/danielmiessler/fabric/to_pdf@latest
```
Make sure you have a LaTeX distribution (like TeX Live or MiKTeX) installed on your system, as `to_pdf` requires `pdflatex` to be available in your system's PATH.

View File

@@ -2,21 +2,26 @@ package cli
import (
"fmt"
"github.com/danielmiessler/fabric/core"
"github.com/danielmiessler/fabric/plugins/ai"
"github.com/danielmiessler/fabric/plugins/db/fsdb"
"github.com/danielmiessler/fabric/plugins/tools/converter"
"github.com/danielmiessler/fabric/restapi"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/danielmiessler/fabric/core"
"github.com/danielmiessler/fabric/db"
)
// Cli Controls the cli. It takes in the flags and runs the appropriate functions
func Cli() (message string, err error) {
func Cli(version string) (err error) {
var currentFlags *Flags
if currentFlags, err = Init(); err != nil {
// we need to reset error, because we don't want to show double help messages
err = nil
return
}
if currentFlags.Version {
fmt.Println(version)
return
}
@@ -25,40 +30,38 @@ func Cli() (message string, err error) {
return
}
fabricDb := db.NewDb(filepath.Join(homedir, ".config/fabric"))
fabricDb := fsdb.NewDb(filepath.Join(homedir, ".config/fabric"))
if err = fabricDb.Configure(); err != nil {
if !currentFlags.Setup {
println(err.Error())
currentFlags.Setup = true
}
}
registry := core.NewPluginRegistry(fabricDb)
// if the setup flag is set, run the setup function
if currentFlags.Setup {
_ = fabricDb.Configure()
_, err = Setup(fabricDb, currentFlags.SetupSkipUpdatePatterns)
err = registry.Setup()
return
}
var fabric *core.Fabric
if err = fabricDb.Configure(); err != nil {
fmt.Println("init is failed, run start the setup procedure", err)
if fabric, err = Setup(fabricDb, currentFlags.SetupSkipUpdatePatterns); err != nil {
return
}
} else {
if fabric, err = core.NewFabric(fabricDb); err != nil {
fmt.Println("fabric can't initialize, please run the --setup procedure", err)
return
}
if currentFlags.Serve {
err = restapi.Serve(registry, currentFlags.ServeAddress)
return
}
// if the update patterns flag is set, run the update patterns function
if currentFlags.UpdatePatterns {
err = fabric.PopulateDB()
err = registry.PatternsLoader.PopulateDB()
return
}
if currentFlags.ChangeDefaultModel {
err = fabric.SetupDefaultModel()
err = registry.Defaults.Setup()
return
}
// if the latest patterns flag is set, run the latest patterns function
if currentFlags.LatestPatterns != "0" {
var parsedToInt int
if parsedToInt, err = strconv.Atoi(currentFlags.LatestPatterns); err != nil {
@@ -71,30 +74,58 @@ func Cli() (message string, err error) {
return
}
// if the list patterns flag is set, run the list all patterns function
if currentFlags.ListPatterns {
err = fabricDb.Patterns.ListNames()
return
}
// if the list all models flag is set, run the list all models function
if currentFlags.ListAllModels {
fabric.GetModels().Print()
var models *ai.VendorsModels
if models, err = registry.VendorManager.GetModels(); err != nil {
return
}
models.Print()
return
}
// if the list all contexts flag is set, run the list all contexts function
if currentFlags.ListAllContexts {
err = fabricDb.Contexts.ListNames()
return
}
// if the list all sessions flag is set, run the list all sessions function
if currentFlags.ListAllSessions {
err = fabricDb.Sessions.ListNames()
return
}
if currentFlags.WipeContext != "" {
err = fabricDb.Contexts.Delete(currentFlags.WipeContext)
return
}
if currentFlags.WipeSession != "" {
err = fabricDb.Sessions.Delete(currentFlags.WipeSession)
return
}
if currentFlags.PrintSession != "" {
err = fabricDb.Sessions.PrintSession(currentFlags.PrintSession)
return
}
if currentFlags.PrintContext != "" {
err = fabricDb.Contexts.PrintContext(currentFlags.PrintContext)
return
}
if currentFlags.HtmlReadability {
if msg, cleanErr := converter.HtmlReadability(currentFlags.Message); cleanErr != nil {
fmt.Println("use original input, because can't apply html readability", err)
} else {
currentFlags.Message = msg
}
}
// if the interactive flag is set, run the interactive function
// if currentFlags.Interactive {
// interactive.Interactive()
@@ -103,93 +134,115 @@ func Cli() (message string, err error) {
// if none of the above currentFlags are set, run the initiate chat function
if currentFlags.YouTube != "" {
if fabric.YouTube.IsConfigured() == false {
if registry.YouTube.IsConfigured() == false {
err = fmt.Errorf("YouTube is not configured, please run the setup procedure")
return
}
var videoId string
if videoId, err = fabric.YouTube.GetVideoId(currentFlags.YouTube); err != nil {
if videoId, err = registry.YouTube.GetVideoId(currentFlags.YouTube); err != nil {
return
}
if !currentFlags.YouTubeComments || currentFlags.YouTubeTranscript {
var transcript string
if transcript, err = fabric.YouTube.GrabTranscript(videoId); err != nil {
var language = "en"
if currentFlags.Language != "" || registry.Language.DefaultLanguage.Value != "" {
if currentFlags.Language != "" {
language = currentFlags.Language
} else {
language = registry.Language.DefaultLanguage.Value
}
}
if transcript, err = registry.YouTube.GrabTranscript(videoId, language); err != nil {
return
}
fmt.Println(transcript)
if currentFlags.Message != "" {
currentFlags.Message = currentFlags.Message + "\n" + transcript
} else {
currentFlags.Message = transcript
}
currentFlags.AppendMessage(transcript)
}
if currentFlags.YouTubeComments {
var comments []string
if comments, err = fabric.YouTube.GrabComments(videoId); err != nil {
if comments, err = registry.YouTube.GrabComments(videoId); err != nil {
return
}
commentsString := strings.Join(comments, "\n")
fmt.Println(commentsString)
if currentFlags.Message != "" {
currentFlags.Message = currentFlags.Message + "\n" + commentsString
} else {
currentFlags.Message = commentsString
}
currentFlags.AppendMessage(commentsString)
}
if currentFlags.Pattern == "" {
if !currentFlags.IsChatRequest() {
// if the pattern flag is not set, we wanted only to grab the transcript or comments
fmt.Println(currentFlags.Message)
return
}
}
if (currentFlags.ScrapeURL != "" || currentFlags.ScrapeQuestion != "") && registry.Jina.IsConfigured() {
// Check if the scrape_url flag is set and call ScrapeURL
if currentFlags.ScrapeURL != "" {
var website string
if website, err = registry.Jina.ScrapeURL(currentFlags.ScrapeURL); err != nil {
return
}
currentFlags.AppendMessage(website)
}
// Check if the scrape_question flag is set and call ScrapeQuestion
if currentFlags.ScrapeQuestion != "" {
var website string
if website, err = registry.Jina.ScrapeQuestion(currentFlags.ScrapeQuestion); err != nil {
return
}
currentFlags.AppendMessage(website)
}
if !currentFlags.IsChatRequest() {
// if the pattern flag is not set, we wanted only to grab the url or get the answer to the question
fmt.Println(currentFlags.Message)
return
}
}
var chatter *core.Chatter
if chatter, err = fabric.GetChatter(currentFlags.Model, currentFlags.Stream, currentFlags.DryRun); err != nil {
if chatter, err = registry.GetChatter(currentFlags.Model, currentFlags.Stream, currentFlags.DryRun); err != nil {
return
}
if message, err = chatter.Send(currentFlags.BuildChatRequest(), currentFlags.BuildChatOptions()); err != nil {
var session *fsdb.Session
chatReq := currentFlags.BuildChatRequest(strings.Join(os.Args[1:], " "))
if chatReq.Language == "" {
chatReq.Language = registry.Language.DefaultLanguage.Value
}
if session, err = chatter.Send(chatReq, currentFlags.BuildChatOptions()); err != nil {
return
}
result := session.GetLastMessage().Content
if !currentFlags.Stream {
fmt.Println(message)
// print the result if it was not streamed already
fmt.Println(result)
}
// if the copy flag is set, copy the message to the clipboard
if currentFlags.Copy {
if err = fabric.CopyToClipboard(message); err != nil {
if err = CopyToClipboard(result); err != nil {
return
}
}
// if the output flag is set, create an output file
if currentFlags.Output != "" {
err = fabric.CreateOutputFile(message, currentFlags.Output)
}
return
}
func Setup(db *db.Db, skipUpdatePatterns bool) (ret *core.Fabric, err error) {
instance := core.NewFabricForSetup(db)
if err = instance.Setup(); err != nil {
return
}
if !skipUpdatePatterns {
if err = instance.PopulateDB(); err != nil {
return
if currentFlags.OutputSession {
sessionAsString := session.String()
err = CreateOutputFile(sessionAsString, currentFlags.Output)
} else {
err = CreateOutputFile(result, currentFlags.Output)
}
}
ret = instance
return
}

View File

@@ -1,23 +1,20 @@
package cli
import (
"github.com/danielmiessler/fabric/core"
"os"
"testing"
"github.com/danielmiessler/fabric/db"
"github.com/stretchr/testify/assert"
)
func TestCli(t *testing.T) {
message, err := Cli()
assert.NoError(t, err)
assert.Empty(t, message)
}
t.Skip("Skipping test for now, collision with flag -t")
originalArgs := os.Args
defer func() { os.Args = originalArgs }()
func TestSetup(t *testing.T) {
mockDB := db.NewDb(os.TempDir())
fabric, err := Setup(mockDB, false)
os.Args = []string{os.Args[0]}
err := Cli("test")
assert.Error(t, err)
assert.Nil(t, fabric)
assert.Equal(t, core.NoSessionPatternUserMessages, err.Error())
}

View File

@@ -9,36 +9,50 @@ import (
"github.com/danielmiessler/fabric/common"
"github.com/jessevdk/go-flags"
"golang.org/x/text/language"
)
// Flags create flags struct. the users flags go into this, this will be passed to the chat struct in cli
type Flags struct {
Pattern string `short:"p" long:"pattern" description:"Choose a pattern" default:""`
PatternVariables map[string]string `short:"v" long:"variable" description:"Values for pattern variables, e.g. -v=$name:John -v=$age:30"`
Context string `short:"C" long:"context" description:"Choose a context" default:""`
Session string `long:"session" description:"Choose a session"`
Setup bool `short:"S" long:"setup" description:"Run setup"`
SetupSkipUpdatePatterns bool `long:"setup-skip-update-patterns" description:"Skip update patterns at setup"`
Temperature float64 `short:"t" long:"temperature" description:"Set temperature" default:"0.7"`
TopP float64 `short:"T" long:"topp" description:"Set top P" default:"0.9"`
Stream bool `short:"s" long:"stream" description:"Stream"`
PresencePenalty float64 `short:"P" long:"presencepenalty" description:"Set presence penalty" default:"0.0"`
FrequencyPenalty float64 `short:"F" long:"frequencypenalty" description:"Set frequency penalty" default:"0.0"`
ListPatterns bool `short:"l" long:"listpatterns" description:"List all patterns"`
ListAllModels bool `short:"L" long:"listmodels" description:"List all available models"`
ListAllContexts bool `short:"x" long:"listcontexts" description:"List all contexts"`
ListAllSessions bool `short:"X" long:"listsessions" description:"List all sessions"`
UpdatePatterns bool `short:"U" long:"updatepatterns" description:"Update patterns"`
Message string `hidden:"true" description:"Message to send to chat"`
Copy bool `short:"c" long:"copy" description:"Copy to clipboard"`
Model string `short:"m" long:"model" description:"Choose model"`
Output string `short:"o" long:"output" description:"Output to file" default:""`
LatestPatterns string `short:"n" long:"latest" description:"Number of latest patterns to list" default:"0"`
ChangeDefaultModel bool `short:"d" long:"changeDefaultModel" description:"Change default pattern"`
YouTube string `short:"y" long:"youtube" description:"YouTube video url to grab transcript, comments from it and send to chat"`
YouTubeTranscript bool `long:"transcript" description:"Grab transcript from YouTube video and send to chat"`
YouTubeComments bool `long:"comments" description:"Grab comments from YouTube video and send to chat"`
DryRun bool `long:"dry-run" description:"Show what would be sent to the model without actually sending it"`
Pattern string `short:"p" long:"pattern" description:"Choose a pattern from the available patterns" default:""`
PatternVariables map[string]string `short:"v" long:"variable" description:"Values for pattern variables, e.g. -v=#role:expert -v=#points:30"`
Context string `short:"C" long:"context" description:"Choose a context from the available contexts" default:""`
Session string `long:"session" description:"Choose a session from the available sessions"`
Setup bool `short:"S" long:"setup" description:"Run setup for all reconfigurable parts of fabric"`
Temperature float64 `short:"t" long:"temperature" description:"Set temperature" default:"0.7"`
TopP float64 `short:"T" long:"topp" description:"Set top P" default:"0.9"`
Stream bool `short:"s" long:"stream" description:"Stream"`
PresencePenalty float64 `short:"P" long:"presencepenalty" description:"Set presence penalty" default:"0.0"`
Raw bool `short:"r" long:"raw" description:"Use the defaults of the model without sending chat options (like temperature etc.) and use the user role instead of the system role for patterns."`
FrequencyPenalty float64 `short:"F" long:"frequencypenalty" description:"Set frequency penalty" default:"0.0"`
ListPatterns bool `short:"l" long:"listpatterns" description:"List all patterns"`
ListAllModels bool `short:"L" long:"listmodels" description:"List all available models"`
ListAllContexts bool `short:"x" long:"listcontexts" description:"List all contexts"`
ListAllSessions bool `short:"X" long:"listsessions" description:"List all sessions"`
UpdatePatterns bool `short:"U" long:"updatepatterns" description:"Update patterns"`
Message string `hidden:"true" description:"Message to send to chat"`
Copy bool `short:"c" long:"copy" description:"Copy to clipboard"`
Model string `short:"m" long:"model" description:"Choose model"`
Output string `short:"o" long:"output" description:"Output to file" default:""`
OutputSession bool `long:"output-session" description:"Output the entire session (also a temporary one) to the output file"`
LatestPatterns string `short:"n" long:"latest" description:"Number of latest patterns to list" default:"0"`
ChangeDefaultModel bool `short:"d" long:"changeDefaultModel" description:"Change default model"`
YouTube string `short:"y" long:"youtube" description:"YouTube video \"URL\" to grab transcript, comments from it and send to chat"`
YouTubeTranscript bool `long:"transcript" description:"Grab transcript from YouTube video and send to chat (it used per default)."`
YouTubeComments bool `long:"comments" description:"Grab comments from YouTube video and send to chat"`
Language string `short:"g" long:"language" description:"Specify the Language Code for the chat, e.g. -g=en -g=zh" default:""`
ScrapeURL string `short:"u" long:"scrape_url" description:"Scrape website URL to markdown using Jina AI"`
ScrapeQuestion string `short:"q" long:"scrape_question" description:"Search question using Jina AI"`
Seed int `short:"e" long:"seed" description:"Seed to be used for LMM generation"`
WipeContext string `short:"w" long:"wipecontext" description:"Wipe context"`
WipeSession string `short:"W" long:"wipesession" description:"Wipe session"`
PrintContext string `long:"printcontext" description:"Print context"`
PrintSession string `long:"printsession" description:"Print session"`
HtmlReadability bool `long:"readability" description:"Convert HTML input into a clean, readable view"`
DryRun bool `long:"dry-run" description:"Show what would be sent to the model without actually sending it"`
Serve bool `long:"serve" description:"Serve the Fabric Rest API"`
ServeAddress string `long:"address" description:"The address to bind the REST API" default:":8080"`
Version bool `long:"version" description:"Print current version"`
}
// Init Initialize flags. returns a Flags struct and an error
@@ -93,17 +107,40 @@ func (o *Flags) BuildChatOptions() (ret *common.ChatOptions) {
TopP: o.TopP,
PresencePenalty: o.PresencePenalty,
FrequencyPenalty: o.FrequencyPenalty,
Raw: o.Raw,
Seed: o.Seed,
}
return
}
func (o *Flags) BuildChatRequest() (ret *common.ChatRequest) {
func (o *Flags) BuildChatRequest(Meta string) (ret *common.ChatRequest) {
ret = &common.ChatRequest{
ContextName: o.Context,
SessionName: o.Session,
PatternName: o.Pattern,
PatternVariables: o.PatternVariables,
Message: o.Message,
Meta: Meta,
}
if o.Language != "" {
langTag, err := language.Parse(o.Language)
if err == nil {
ret.Language = langTag.String()
}
}
return
}
func (o *Flags) AppendMessage(message string) {
if o.Message != "" {
o.Message = o.Message + "\n" + message
} else {
o.Message = message
}
return
}
func (o *Flags) IsChatRequest() (ret bool) {
ret = (o.Message != "" || o.Context != "") && (o.Session != "" || o.Pattern != "")
return
}

View File

@@ -53,6 +53,7 @@ func TestBuildChatOptions(t *testing.T) {
TopP: 0.9,
PresencePenalty: 0.1,
FrequencyPenalty: 0.2,
Seed: 1,
}
expectedOptions := &common.ChatOptions{
@@ -60,6 +61,28 @@ func TestBuildChatOptions(t *testing.T) {
TopP: 0.9,
PresencePenalty: 0.1,
FrequencyPenalty: 0.2,
Raw: false,
Seed: 1,
}
options := flags.BuildChatOptions()
assert.Equal(t, expectedOptions, options)
}
func TestBuildChatOptionsDefaultSeed(t *testing.T) {
flags := &Flags{
Temperature: 0.8,
TopP: 0.9,
PresencePenalty: 0.1,
FrequencyPenalty: 0.2,
}
expectedOptions := &common.ChatOptions{
Temperature: 0.8,
TopP: 0.9,
PresencePenalty: 0.1,
FrequencyPenalty: 0.2,
Raw: false,
Seed: 0,
}
options := flags.BuildChatOptions()
assert.Equal(t, expectedOptions, options)
@@ -78,7 +101,8 @@ func TestBuildChatRequest(t *testing.T) {
SessionName: "test-session",
PatternName: "test-pattern",
Message: "test-message",
Meta: "test",
}
request := flags.BuildChatRequest()
request := flags.BuildChatRequest("test")
assert.Equal(t, expectedRequest, request)
}

27
cli/output.go Normal file
View File

@@ -0,0 +1,27 @@
package cli
import (
"fmt"
"github.com/atotto/clipboard"
"os"
)
func CopyToClipboard(message string) (err error) {
if err = clipboard.WriteAll(message); err != nil {
err = fmt.Errorf("could not copy to clipboard: %v", err)
}
return
}
func CreateOutputFile(message string, fileName string) (err error) {
var file *os.File
if file, err = os.Create(fileName); err != nil {
err = fmt.Errorf("error creating file: %v", err)
return
}
defer file.Close()
if _, err = file.WriteString(message); err != nil {
err = fmt.Errorf("error writing to file: %v", err)
}
return
}

28
cli/output_test.go Normal file
View File

@@ -0,0 +1,28 @@
package cli
import (
"os"
"testing"
)
func TestCopyToClipboard(t *testing.T) {
t.Skip("skipping test, because of docker env. in ci.")
message := "test message"
err := CopyToClipboard(message)
if err != nil {
t.Fatalf("CopyToClipboard() error = %v", err)
}
}
func TestCreateOutputFile(t *testing.T) {
fileName := "test_output.txt"
message := "test message"
err := CreateOutputFile(message, fileName)
if err != nil {
t.Fatalf("CreateOutputFile() error = %v", err)
}
defer os.Remove(fileName)
}

View File

@@ -1,5 +1,9 @@
package common
import goopenai "github.com/sashabaranov/go-openai"
const ChatMessageRoleMeta = "meta"
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
@@ -11,6 +15,8 @@ type ChatRequest struct {
PatternName string
PatternVariables map[string]string
Message string
Language string
Meta string
}
type ChatOptions struct {
@@ -19,6 +25,8 @@ type ChatOptions struct {
TopP float64
PresencePenalty float64
FrequencyPenalty float64
Raw bool
Seed int
}
// NormalizeMessages remove empty messages and ensure messages order user-assist-user
@@ -32,8 +40,8 @@ func NormalizeMessages(msgs []*Message, defaultUserMessage string) (ret []*Messa
}
// Ensure, that each odd position shall be a user message
if fullMessageIndex%2 == 0 && message.Role != "user" {
ret = append(ret, &Message{Role: "user", Content: defaultUserMessage})
if fullMessageIndex%2 == 0 && message.Role != goopenai.ChatMessageRoleUser {
ret = append(ret, &Message{Role: goopenai.ChatMessageRoleUser, Content: defaultUserMessage})
fullMessageIndex++
}
ret = append(ret, message)

View File

@@ -1,23 +1,24 @@
package common
import (
goopenai "github.com/sashabaranov/go-openai"
"github.com/stretchr/testify/assert"
"testing"
)
func TestNormalizeMessages(t *testing.T) {
msgs := []*Message{
{Role: "user", Content: "Hello"},
{Role: "bot", Content: "Hi there!"},
{Role: "bot", Content: ""},
{Role: "user", Content: ""},
{Role: "user", Content: "How are you?"},
{Role: goopenai.ChatMessageRoleUser, Content: "Hello"},
{Role: goopenai.ChatMessageRoleAssistant, Content: "Hi there!"},
{Role: goopenai.ChatMessageRoleUser, Content: ""},
{Role: goopenai.ChatMessageRoleUser, Content: ""},
{Role: goopenai.ChatMessageRoleUser, Content: "How are you?"},
}
expected := []*Message{
{Role: "user", Content: "Hello"},
{Role: "bot", Content: "Hi there!"},
{Role: "user", Content: "How are you?"},
{Role: goopenai.ChatMessageRoleUser, Content: "Hello"},
{Role: goopenai.ChatMessageRoleAssistant, Content: "Hi there!"},
{Role: goopenai.ChatMessageRoleUser, Content: "How are you?"},
}
actual := NormalizeMessages(msgs, "default")

134
common/groups_items.go Normal file
View File

@@ -0,0 +1,134 @@
package common
import (
"fmt"
"github.com/samber/lo"
)
func NewGroupsItemsSelector[I any](selectionLabel string,
getItemLabel func(I) string) *GroupsItemsSelector[I] {
return &GroupsItemsSelector[I]{SelectionLabel: selectionLabel,
GetItemKey: getItemLabel,
GroupsItems: make([]*GroupItems[I], 0),
}
}
type GroupItems[I any] struct {
Group string
Items []I
}
func (o *GroupItems[I]) Count() int {
return len(o.Items)
}
func (o *GroupItems[I]) ContainsItemBy(predicate func(item I) bool) (ret bool) {
ret = lo.ContainsBy(o.Items, predicate)
return
}
type GroupsItemsSelector[I any] struct {
SelectionLabel string
GetItemKey func(I) string
GroupsItems []*GroupItems[I]
}
func (o *GroupsItemsSelector[I]) AddGroupItems(group string, items ...I) {
o.GroupsItems = append(o.GroupsItems, &GroupItems[I]{group, items})
}
func (o *GroupsItemsSelector[I]) GetGroupAndItemByItemNumber(number int) (group string, item I, err error) {
var currentItemNumber int
found := false
for _, groupItems := range o.GroupsItems {
if currentItemNumber+groupItems.Count() < number {
currentItemNumber += groupItems.Count()
continue
}
for _, groupItem := range groupItems.Items {
currentItemNumber++
if currentItemNumber == number {
group = groupItems.Group
item = groupItem
found = true
break
}
}
}
if !found {
err = fmt.Errorf("number %d is out of range", number)
}
return
}
func (o *GroupsItemsSelector[I]) Print() {
fmt.Printf("\n%v:\n", o.SelectionLabel)
var currentItemIndex int
for _, groupItems := range o.GroupsItems {
fmt.Println()
fmt.Printf("%s\n", groupItems.Group)
fmt.Println()
for _, item := range groupItems.Items {
currentItemIndex++
fmt.Printf("\t[%d]\t%s\n", currentItemIndex, o.GetItemKey(item))
}
}
}
func (o *GroupsItemsSelector[I]) HasGroup(group string) (ret bool) {
for _, groupItems := range o.GroupsItems {
if ret = groupItems.Group == group; ret {
break
}
}
return
}
func (o *GroupsItemsSelector[I]) FindGroupsByItemFirst(item I) (ret string) {
itemKey := o.GetItemKey(item)
for _, groupItems := range o.GroupsItems {
if groupItems.ContainsItemBy(func(groupItem I) bool {
groupItemKey := o.GetItemKey(groupItem)
return groupItemKey == itemKey
}) {
ret = groupItems.Group
break
}
}
return
}
func (o *GroupsItemsSelector[I]) FindGroupsByItem(item I) (groups []string) {
itemKey := o.GetItemKey(item)
for _, groupItems := range o.GroupsItems {
if groupItems.ContainsItemBy(func(groupItem I) bool {
groupItemKey := o.GetItemKey(groupItem)
return groupItemKey == itemKey
}) {
groups = append(groups, groupItems.Group)
}
}
return
}
func ReturnItem(item string) string {
return item
}
func NewGroupsItemsSelectorString(selectionLabel string) *GroupsItemsSelectorString {
return &GroupsItemsSelectorString{GroupsItemsSelector: NewGroupsItemsSelector(selectionLabel, ReturnItem)}
}
type GroupsItemsSelectorString struct {
*GroupsItemsSelector[string]
}

View File

@@ -3,30 +3,27 @@ package core
import (
"context"
"fmt"
"github.com/danielmiessler/fabric/common"
"github.com/danielmiessler/fabric/db"
"github.com/danielmiessler/fabric/vendors"
"github.com/danielmiessler/fabric/plugins/ai"
"github.com/danielmiessler/fabric/plugins/db/fsdb"
goopenai "github.com/sashabaranov/go-openai"
"strings"
)
const NoSessionPatternUserMessages = "no session, pattern or user messages provided"
type Chatter struct {
db *db.Db
db *fsdb.Db
Stream bool
DryRun bool
model string
vendor vendors.Vendor
vendor ai.Vendor
}
func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (message string, err error) {
var chatRequest *Chat
if chatRequest, err = o.NewChat(request); err != nil {
return
}
var session *db.Session
if session, err = chatRequest.BuildChatSession(); err != nil {
func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (session *fsdb.Session, err error) {
if session, err = o.BuildSession(request, opts.Raw); err != nil {
return
}
@@ -34,10 +31,12 @@ func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (m
opts.Model = o.model
}
message := ""
if o.Stream {
channel := make(chan string)
go func() {
if streamErr := o.vendor.SendStream(session.Messages, opts, channel); streamErr != nil {
if streamErr := o.vendor.SendStream(session.GetVendorMessages(), opts, channel); streamErr != nil {
channel <- streamErr.Error()
}
}()
@@ -47,58 +46,88 @@ func (o *Chatter) Send(request *common.ChatRequest, opts *common.ChatOptions) (m
fmt.Print(response)
}
} else {
if message, err = o.vendor.Send(context.Background(), session.Messages, opts); err != nil {
if message, err = o.vendor.Send(context.Background(), session.GetVendorMessages(), opts); err != nil {
return
}
}
if chatRequest.Session != nil && message != "" {
chatRequest.Session.Append(&common.Message{Role: "system", Content: message})
err = o.db.Sessions.SaveSession(chatRequest.Session)
if message == "" {
session = nil
err = fmt.Errorf("empty response")
return
}
session.Append(&common.Message{Role: goopenai.ChatMessageRoleAssistant, Content: message})
if session.Name != "" {
err = o.db.Sessions.SaveSession(session)
}
return
}
func (o *Chatter) NewChat(request *common.ChatRequest) (ret *Chat, err error) {
ret = &Chat{}
if request.ContextName != "" {
var ctx *db.Context
if ctx, err = o.db.Contexts.GetContext(request.ContextName); err != nil {
err = fmt.Errorf("could not find context %s: %v", request.ContextName, err)
return
}
ret.Context = ctx.Content
}
func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session *fsdb.Session, err error) {
if request.SessionName != "" {
var sess *db.Session
if sess, err = o.db.Sessions.GetOrCreateSession(request.SessionName); err != nil {
var sess *fsdb.Session
if sess, err = o.db.Sessions.Get(request.SessionName); err != nil {
err = fmt.Errorf("could not find session %s: %v", request.SessionName, err)
return
}
ret.Session = sess
session = sess
} else {
session = &fsdb.Session{}
}
if request.Meta != "" {
session.Append(&common.Message{Role: common.ChatMessageRoleMeta, Content: request.Meta})
}
var contextContent string
if request.ContextName != "" {
var ctx *fsdb.Context
if ctx, err = o.db.Contexts.Get(request.ContextName); err != nil {
err = fmt.Errorf("could not find context %s: %v", request.ContextName, err)
return
}
contextContent = ctx.Content
}
var patternContent string
if request.PatternName != "" {
var pattern *db.Pattern
if pattern, err = o.db.Patterns.GetPattern(request.PatternName, request.PatternVariables); err != nil {
var pattern *fsdb.Pattern
if pattern, err = o.db.Patterns.GetApplyVariables(request.PatternName, request.PatternVariables); err != nil {
err = fmt.Errorf("could not find pattern %s: %v", request.PatternName, err)
return
}
if pattern.Pattern != "" {
ret.Pattern = pattern.Pattern
patternContent = pattern.Pattern
}
}
ret.Message = request.Message
systemMessage := strings.TrimSpace(contextContent) + strings.TrimSpace(patternContent)
if request.Language != "" {
systemMessage = fmt.Sprintf("%s. Please use the language '%s' for the output.", systemMessage, request.Language)
}
userMessage := strings.TrimSpace(request.Message)
if raw {
// use the user role instead of the system role in raw mode
message := systemMessage + userMessage
if message != "" {
session.Append(&common.Message{Role: goopenai.ChatMessageRoleUser, Content: message})
}
} else {
if systemMessage != "" {
session.Append(&common.Message{Role: goopenai.ChatMessageRoleSystem, Content: systemMessage})
}
if userMessage != "" {
session.Append(&common.Message{Role: goopenai.ChatMessageRoleUser, Content: userMessage})
}
}
if session.IsEmpty() {
session = nil
err = fmt.Errorf(NoSessionPatternUserMessages)
}
return
}
type Chat struct {
Context string
Pattern string
Message string
Session *db.Session
}

View File

@@ -1,21 +0,0 @@
package core
import (
"testing"
)
func TestBuildChatSession(t *testing.T) {
chat := &Chat{
Context: "test context",
Pattern: "test pattern",
Message: "test message",
}
session, err := chat.BuildChatSession()
if err != nil {
t.Fatalf("BuildChatSession() error = %v", err)
}
if session == nil {
t.Fatalf("BuildChatSession() returned nil session")
}
}

View File

@@ -1,263 +0,0 @@
package core
import (
"bytes"
"fmt"
"github.com/danielmiessler/fabric/vendors/groq"
"os"
"strconv"
"strings"
"github.com/atotto/clipboard"
"github.com/danielmiessler/fabric/common"
"github.com/danielmiessler/fabric/db"
"github.com/danielmiessler/fabric/vendors/anthropic"
"github.com/danielmiessler/fabric/vendors/azure"
"github.com/danielmiessler/fabric/vendors/dryrun"
"github.com/danielmiessler/fabric/vendors/gemini"
"github.com/danielmiessler/fabric/vendors/ollama"
"github.com/danielmiessler/fabric/vendors/openai"
"github.com/danielmiessler/fabric/vendors/openrouter"
"github.com/danielmiessler/fabric/vendors/siliconcloud"
"github.com/danielmiessler/fabric/youtube"
"github.com/pkg/errors"
)
const DefaultPatternsGitRepoUrl = "https://github.com/danielmiessler/fabric.git"
const DefaultPatternsGitRepoFolder = "patterns"
func NewFabric(db *db.Db) (ret *Fabric, err error) {
ret = NewFabricBase(db)
err = ret.Configure()
return
}
func NewFabricForSetup(db *db.Db) (ret *Fabric) {
ret = NewFabricBase(db)
_ = ret.Configure()
return
}
// NewFabricBase Create a new Fabric from a list of already configured VendorsController
func NewFabricBase(db *db.Db) (ret *Fabric) {
ret = &Fabric{
VendorsManager: NewVendorsManager(),
Db: db,
VendorsAll: NewVendorsManager(),
PatternsLoader: NewPatternsLoader(db.Patterns),
YouTube: youtube.NewYouTube(),
}
label := "Default"
ret.Configurable = &common.Configurable{
Label: label,
EnvNamePrefix: common.BuildEnvVariablePrefix(label),
ConfigureCustom: ret.configure,
}
ret.DefaultVendor = ret.AddSetting("Vendor", true)
ret.DefaultModel = ret.AddSetupQuestionCustom("Model", true,
"Enter the index the name of your default model")
ret.VendorsAll.AddVendors(openai.NewClient(), azure.NewClient(), ollama.NewClient(), groq.NewClient(),
gemini.NewClient(), anthropic.NewClient(), siliconcloud.NewClient(), openrouter.NewClient())
return
}
type Fabric struct {
*common.Configurable
*VendorsManager
VendorsAll *VendorsManager
*PatternsLoader
*youtube.YouTube
Db *db.Db
DefaultVendor *common.Setting
DefaultModel *common.SetupQuestion
}
type ChannelName struct {
channel chan []string
name string
}
func (o *Fabric) SaveEnvFile() (err error) {
// Now create the .env with all configured VendorsController info
var envFileContent bytes.Buffer
o.Settings.FillEnvFileContent(&envFileContent)
o.PatternsLoader.SetupFillEnvFileContent(&envFileContent)
for _, vendor := range o.Vendors {
vendor.SetupFillEnvFileContent(&envFileContent)
}
o.YouTube.SetupFillEnvFileContent(&envFileContent)
err = o.Db.SaveEnv(envFileContent.String())
return
}
func (o *Fabric) Setup() (err error) {
if err = o.SetupVendors(); err != nil {
return
}
if err = o.SetupDefaultModel(); err != nil {
return
}
_ = o.YouTube.SetupOrSkip()
if err = o.PatternsLoader.Setup(); err != nil {
return
}
err = o.SaveEnvFile()
return
}
func (o *Fabric) SetupDefaultModel() (err error) {
vendorsModels := o.GetModels()
vendorsModels.Print()
if err = o.Ask(o.Label); err != nil {
return
}
index, parseErr := strconv.Atoi(o.DefaultModel.Value)
if parseErr == nil {
o.DefaultVendor.Value, o.DefaultModel.Value = vendorsModels.GetVendorAndModelByModelIndex(index)
} else {
o.DefaultVendor.Value = vendorsModels.FindVendorsByModelFirst(o.DefaultModel.Value)
}
//verify
vendorNames := vendorsModels.FindVendorsByModel(o.DefaultModel.Value)
if len(vendorNames) == 0 {
err = errors.Errorf("You need to chose an available default model.")
return
}
fmt.Println()
o.DefaultVendor.Print()
o.DefaultModel.Print()
err = o.SaveEnvFile()
return
}
func (o *Fabric) SetupVendors() (err error) {
o.Models = nil
if o.Vendors, err = o.VendorsAll.Setup(); err != nil {
return
}
if !o.HasVendors() {
err = errors.New("No vendors configured")
return
}
err = o.SaveEnvFile()
return
}
// Configure buildClient VendorsController based on the environment variables
func (o *Fabric) configure() (err error) {
for _, vendor := range o.VendorsAll.Vendors {
if vendorErr := vendor.Configure(); vendorErr == nil {
o.AddVendors(vendor)
}
}
if err = o.PatternsLoader.Configure(); err != nil {
return
}
//YouTube is not mandatory, so ignore not configured error
_ = o.YouTube.Configure()
return
}
func (o *Fabric) GetChatter(model string, stream bool, dryRun bool) (ret *Chatter, err error) {
ret = &Chatter{
db: o.Db,
Stream: stream,
DryRun: dryRun,
}
if dryRun {
ret.vendor = dryrun.NewClient()
ret.model = model
if ret.model == "" {
ret.model = o.DefaultModel.Value
}
} else if model == "" {
ret.vendor = o.FindByName(o.DefaultVendor.Value)
ret.model = o.DefaultModel.Value
} else {
ret.vendor = o.FindByName(o.GetModels().FindVendorsByModelFirst(model))
ret.model = model
}
if ret.vendor == nil {
err = fmt.Errorf(
"could not find vendor.\n Model = %s\n DefaultModel = %s\n DefaultVendor = %s",
model, o.DefaultModel.Value, o.DefaultVendor.Value)
return
}
return
}
func (o *Fabric) CopyToClipboard(message string) (err error) {
if err = clipboard.WriteAll(message); err != nil {
err = fmt.Errorf("could not copy to clipboard: %v", err)
}
return
}
func (o *Fabric) CreateOutputFile(message string, fileName string) (err error) {
var file *os.File
if file, err = os.Create(fileName); err != nil {
err = fmt.Errorf("error creating file: %v", err)
return
}
defer file.Close()
if _, err = file.WriteString(message); err != nil {
err = fmt.Errorf("error writing to file: %v", err)
}
return
}
func (o *Chat) BuildChatSession() (ret *db.Session, err error) {
// new messages will be appended to the session and used to send the message
if o.Session != nil {
ret = o.Session
} else {
ret = &db.Session{}
}
systemMessage := strings.TrimSpace(o.Context) + strings.TrimSpace(o.Pattern)
if systemMessage != "" {
ret.Append(&common.Message{Role: "system", Content: systemMessage})
}
userMessage := strings.TrimSpace(o.Message)
if userMessage != "" {
ret.Append(&common.Message{Role: "user", Content: userMessage})
}
if ret.IsEmpty() {
ret = nil
err = fmt.Errorf("no session, pattern or user messages provided")
}
return
}

View File

@@ -1,49 +0,0 @@
package core
import (
"os"
"testing"
"github.com/danielmiessler/fabric/db"
)
func TestNewFabric(t *testing.T) {
_, err := NewFabric(db.NewDb(os.TempDir()))
if err == nil {
t.Fatal("without setup error expected")
}
}
func TestSaveEnvFile(t *testing.T) {
fabric := NewFabricBase(db.NewDb(os.TempDir()))
err := fabric.SaveEnvFile()
if err != nil {
t.Fatalf("SaveEnvFile() error = %v", err)
}
}
func TestCopyToClipboard(t *testing.T) {
t.Skip("skipping test, because of docker env. in ci.")
fabric := NewFabricBase(db.NewDb(os.TempDir()))
message := "test message"
err := fabric.CopyToClipboard(message)
if err != nil {
t.Fatalf("CopyToClipboard() error = %v", err)
}
}
func TestCreateOutputFile(t *testing.T) {
mockDb := &db.Db{}
fabric := NewFabricBase(mockDb)
fileName := "test_output.txt"
message := "test message"
err := fabric.CreateOutputFile(message, fileName)
if err != nil {
t.Fatalf("CreateOutputFile() error = %v", err)
}
defer os.Remove(fileName)
}

View File

@@ -1,97 +0,0 @@
package core
import (
"fmt"
"sort"
)
func NewVendorsModels() *VendorsModels {
return &VendorsModels{VendorsModels: make(map[string][]string)}
}
type VendorsModels struct {
Vendors []string
VendorsModels map[string][]string
Errs []error
}
func (o *VendorsModels) AddVendorModels(vendor string, models []string) {
o.Vendors = append(o.Vendors, vendor)
o.VendorsModels[vendor] = models
}
func (o *VendorsModels) GetVendorAndModelByModelIndex(modelIndex int) (vendor string, model string) {
vendorModelIndexFrom := 0
vendorModelIndexTo := 0
for _, currenVendor := range o.Vendors {
vendorModelIndexFrom = vendorModelIndexTo + 1
vendorModelIndexTo = vendorModelIndexFrom + len(o.VendorsModels[currenVendor]) - 1
if modelIndex >= vendorModelIndexFrom && modelIndex <= vendorModelIndexTo {
vendor = currenVendor
model = o.VendorsModels[currenVendor][modelIndex-vendorModelIndexFrom]
break
}
}
return
}
func (o *VendorsModels) AddError(err error) {
o.Errs = append(o.Errs, err)
}
func (o *VendorsModels) Print() {
fmt.Printf("\nAvailable vendor models:\n")
sort.Strings(o.Vendors)
var currentModelIndex int
for _, vendor := range o.Vendors {
fmt.Println()
fmt.Printf("%s\n", vendor)
fmt.Println()
currentModelIndex = o.PrintVendor(vendor, currentModelIndex)
}
return
}
func (o *VendorsModels) PrintVendor(vendor string, modelIndex int) (currentModelIndex int) {
currentModelIndex = modelIndex
models := o.VendorsModels[vendor]
for _, model := range models {
currentModelIndex++
fmt.Printf("\t[%d]\t%s\n", currentModelIndex, model)
}
fmt.Println()
return
}
func (o *VendorsModels) GetVendorModels(vendor string) (models []string) {
models = o.VendorsModels[vendor]
return
}
func (o *VendorsModels) HasVendor(vendor string) (ret bool) {
ret = o.VendorsModels[vendor] != nil
return
}
func (o *VendorsModels) FindVendorsByModelFirst(model string) (ret string) {
vendors := o.FindVendorsByModel(model)
if len(vendors) > 0 {
ret = vendors[0]
}
return
}
func (o *VendorsModels) FindVendorsByModel(model string) (vendors []string) {
for vendor, models := range o.VendorsModels {
for _, m := range models {
if m == model {
vendors = append(vendors, vendor)
continue
}
}
}
return
}

View File

@@ -1,52 +0,0 @@
package core
import (
"errors"
"testing"
)
func TestNewVendorsModels(t *testing.T) {
vendors := NewVendorsModels()
if vendors == nil {
t.Fatalf("NewVendorsModels() returned nil")
}
if len(vendors.VendorsModels) != 0 {
t.Fatalf("NewVendorsModels() returned non-empty VendorsModels map")
}
}
func TestFindVendorsByModelFirst(t *testing.T) {
vendors := NewVendorsModels()
vendors.AddVendorModels("vendor1", []string{"model1", "model2"})
vendor := vendors.FindVendorsByModelFirst("model1")
if vendor != "vendor1" {
t.Fatalf("FindVendorsByModelFirst() = %v, want %v", vendor, "vendor1")
}
}
func TestFindVendorsByModel(t *testing.T) {
vendors := NewVendorsModels()
vendors.AddVendorModels("vendor1", []string{"model1", "model2"})
foundVendors := vendors.FindVendorsByModel("model1")
if len(foundVendors) != 1 || foundVendors[0] != "vendor1" {
t.Fatalf("FindVendorsByModel() = %v, want %v", foundVendors, []string{"vendor1"})
}
}
func TestAddVendorModels(t *testing.T) {
vendors := NewVendorsModels()
vendors.AddVendorModels("vendor1", []string{"model1", "model2"})
models := vendors.GetVendorModels("vendor1")
if len(models) != 2 {
t.Fatalf("AddVendorModels() failed to add models")
}
}
func TestAddError(t *testing.T) {
vendors := NewVendorsModels()
err := errors.New("sample error")
vendors.AddError(err)
if len(vendors.Errs) != 1 {
t.Fatalf("AddError() failed to add error")
}
}

203
core/plugin_registry.go Normal file
View File

@@ -0,0 +1,203 @@
package core
import (
"bytes"
"fmt"
"github.com/danielmiessler/fabric/common"
"github.com/danielmiessler/fabric/plugins/ai/azure"
"github.com/danielmiessler/fabric/plugins/tools"
"github.com/samber/lo"
"strconv"
"github.com/danielmiessler/fabric/plugins"
"github.com/danielmiessler/fabric/plugins/ai"
"github.com/danielmiessler/fabric/plugins/ai/anthropic"
"github.com/danielmiessler/fabric/plugins/ai/dryrun"
"github.com/danielmiessler/fabric/plugins/ai/gemini"
"github.com/danielmiessler/fabric/plugins/ai/groq"
"github.com/danielmiessler/fabric/plugins/ai/mistral"
"github.com/danielmiessler/fabric/plugins/ai/ollama"
"github.com/danielmiessler/fabric/plugins/ai/openai"
"github.com/danielmiessler/fabric/plugins/ai/openrouter"
"github.com/danielmiessler/fabric/plugins/ai/siliconcloud"
"github.com/danielmiessler/fabric/plugins/db/fsdb"
"github.com/danielmiessler/fabric/plugins/tools/jina"
"github.com/danielmiessler/fabric/plugins/tools/lang"
"github.com/danielmiessler/fabric/plugins/tools/youtube"
)
func NewPluginRegistry(db *fsdb.Db) (ret *PluginRegistry) {
ret = &PluginRegistry{
Db: db,
VendorManager: ai.NewVendorsManager(),
VendorsAll: ai.NewVendorsManager(),
PatternsLoader: tools.NewPatternsLoader(db.Patterns),
YouTube: youtube.NewYouTube(),
Language: lang.NewLanguage(),
Jina: jina.NewClient(),
}
ret.Defaults = tools.NeeDefaults(ret.VendorManager.GetModels)
ret.VendorsAll.AddVendors(openai.NewClient(), ollama.NewClient(), azure.NewClient(), groq.NewClient(),
gemini.NewClient(), anthropic.NewClient(), siliconcloud.NewClient(), openrouter.NewClient(), mistral.NewClient())
_ = ret.Configure()
return
}
type PluginRegistry struct {
Db *fsdb.Db
VendorManager *ai.VendorsManager
VendorsAll *ai.VendorsManager
Defaults *tools.Defaults
PatternsLoader *tools.PatternsLoader
YouTube *youtube.YouTube
Language *lang.Language
Jina *jina.Client
}
func (o *PluginRegistry) SaveEnvFile() (err error) {
// Now create the .env with all configured VendorsController info
var envFileContent bytes.Buffer
o.Defaults.Settings.FillEnvFileContent(&envFileContent)
o.PatternsLoader.SetupFillEnvFileContent(&envFileContent)
for _, vendor := range o.VendorManager.Vendors {
vendor.SetupFillEnvFileContent(&envFileContent)
}
o.YouTube.SetupFillEnvFileContent(&envFileContent)
o.Jina.SetupFillEnvFileContent(&envFileContent)
o.Language.SetupFillEnvFileContent(&envFileContent)
err = o.Db.SaveEnv(envFileContent.String())
return
}
func (o *PluginRegistry) Setup() (err error) {
setupQuestion := plugins.NewSetupQuestion("Enter the number of the plugin to setup")
groupsPlugins := common.NewGroupsItemsSelector[plugins.Plugin]("Available plugins",
func(plugin plugins.Plugin) string {
var configuredLabel string
if plugin.IsConfigured() {
configuredLabel = " (configured)"
} else {
configuredLabel = ""
}
return fmt.Sprintf("%v%v", plugin.GetSetupDescription(), configuredLabel)
})
groupsPlugins.AddGroupItems("AI Vendors [at least one, required]", lo.Map(o.VendorsAll.Vendors,
func(vendor ai.Vendor, _ int) plugins.Plugin {
return vendor
})...)
groupsPlugins.AddGroupItems("Tools", o.Defaults, o.PatternsLoader, o.YouTube, o.Language, o.Jina)
for {
groupsPlugins.Print()
if answerErr := setupQuestion.Ask("Plugin Number"); answerErr != nil {
break
}
if setupQuestion.Value == "" {
break
}
number, parseErr := strconv.Atoi(setupQuestion.Value)
setupQuestion.Value = ""
if parseErr == nil {
var plugin plugins.Plugin
if _, plugin, err = groupsPlugins.GetGroupAndItemByItemNumber(number); err != nil {
return
}
if pluginSetupErr := plugin.Setup(); pluginSetupErr != nil {
println(pluginSetupErr.Error())
} else {
if err = o.SaveEnvFile(); err != nil {
break
}
}
if _, ok := o.VendorManager.VendorsByName[plugin.GetName()]; !ok {
if vendor, ok := plugin.(ai.Vendor); ok {
o.VendorManager.AddVendors(vendor)
}
}
} else {
break
}
}
err = o.SaveEnvFile()
return
}
func (o *PluginRegistry) SetupVendor(vendorName string) (err error) {
if err = o.VendorsAll.SetupVendor(vendorName, o.VendorManager.VendorsByName); err != nil {
return
}
err = o.SaveEnvFile()
return
}
// Configure buildClient VendorsController based on the environment variables
func (o *PluginRegistry) Configure() (err error) {
for _, vendor := range o.VendorsAll.Vendors {
if vendorErr := vendor.Configure(); vendorErr == nil {
o.VendorManager.AddVendors(vendor)
}
}
_ = o.Defaults.Configure()
_ = o.PatternsLoader.Configure()
//YouTube and Jina are not mandatory, so ignore not configured error
_ = o.YouTube.Configure()
_ = o.Jina.Configure()
_ = o.Language.Configure()
return
}
func (o *PluginRegistry) GetChatter(model string, stream bool, dryRun bool) (ret *Chatter, err error) {
ret = &Chatter{
db: o.Db,
Stream: stream,
DryRun: dryRun,
}
defaultModel := o.Defaults.Model.Value
defaultVendor := o.Defaults.Vendor.Value
vendorManager := o.VendorManager
if dryRun {
ret.vendor = dryrun.NewClient()
ret.model = model
if ret.model == "" {
ret.model = defaultModel
}
} else if model == "" {
ret.vendor = vendorManager.FindByName(defaultVendor)
ret.model = defaultModel
} else {
var models *ai.VendorsModels
if models, err = vendorManager.GetModels(); err != nil {
return
}
ret.vendor = vendorManager.FindByName(models.FindGroupsByItemFirst(model))
ret.model = model
}
if ret.vendor == nil {
err = fmt.Errorf(
"could not find vendor.\n Model = %s\n Model = %s\n Vendor = %s",
model, defaultModel, defaultVendor)
return
}
return
}

View File

@@ -0,0 +1,16 @@
package core
import (
"github.com/danielmiessler/fabric/plugins/db/fsdb"
"os"
"testing"
)
func TestSaveEnvFile(t *testing.T) {
registry := NewPluginRegistry(fsdb.NewDb(os.TempDir()))
err := registry.SaveEnvFile()
if err != nil {
t.Fatalf("SaveEnvFile() error = %v", err)
}
}

View File

@@ -1,107 +0,0 @@
package core
import (
"context"
"fmt"
"sync"
"github.com/danielmiessler/fabric/vendors"
)
func NewVendorsManager() *VendorsManager {
return &VendorsManager{
Vendors: map[string]vendors.Vendor{},
}
}
type VendorsManager struct {
Vendors map[string]vendors.Vendor
Models *VendorsModels
}
func (o *VendorsManager) AddVendors(vendors ...vendors.Vendor) {
for _, vendor := range vendors {
o.Vendors[vendor.GetName()] = vendor
}
}
func (o *VendorsManager) GetModels() *VendorsModels {
if o.Models == nil {
o.readModels()
}
return o.Models
}
func (o *VendorsManager) HasVendors() bool {
return len(o.Vendors) > 0
}
func (o *VendorsManager) FindByName(name string) vendors.Vendor {
return o.Vendors[name]
}
func (o *VendorsManager) readModels() {
o.Models = NewVendorsModels()
var wg sync.WaitGroup
resultsChan := make(chan modelResult, len(o.Vendors))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for _, vendor := range o.Vendors {
wg.Add(1)
go o.fetchVendorModels(ctx, &wg, vendor, resultsChan)
}
// Wait for all goroutines to finish
go func() {
wg.Wait()
close(resultsChan)
}()
// Collect results
for result := range resultsChan {
if result.err != nil {
fmt.Println(result.vendorName, result.err)
o.Models.AddError(result.err)
cancel() // Cancel remaining goroutines if needed
} else {
o.Models.AddVendorModels(result.vendorName, result.models)
}
}
}
func (o *VendorsManager) fetchVendorModels(
ctx context.Context, wg *sync.WaitGroup, vendor vendors.Vendor, resultsChan chan<- modelResult) {
defer wg.Done()
models, err := vendor.ListModels()
select {
case <-ctx.Done():
// Context canceled, don't send the result
return
case resultsChan <- modelResult{vendorName: vendor.GetName(), models: models, err: err}:
// Result sent
}
}
func (o *VendorsManager) Setup() (ret map[string]vendors.Vendor, err error) {
ret = map[string]vendors.Vendor{}
for _, vendor := range o.Vendors {
fmt.Println()
if vendorErr := vendor.Setup(); vendorErr == nil {
fmt.Printf("[%v] configured\n", vendor.GetName())
ret[vendor.GetName()] = vendor
} else {
fmt.Printf("[%v] skipped\n", vendor.GetName())
}
}
return
}
type modelResult struct {
vendorName string
models []string
err error
}

View File

@@ -1,131 +0,0 @@
package core
import (
"bytes"
"context"
"testing"
"github.com/danielmiessler/fabric/common"
)
func TestNewVendorsManager(t *testing.T) {
vendorsManager := NewVendorsManager()
if vendorsManager == nil {
t.Fatalf("NewVendorsManager() returned nil")
}
}
func TestAddVendors(t *testing.T) {
vendorsManager := NewVendorsManager()
mockVendor := &MockVendor{name: "testVendor"}
vendorsManager.AddVendors(mockVendor)
if _, exists := vendorsManager.Vendors[mockVendor.GetName()]; !exists {
t.Fatalf("AddVendors() did not add vendor")
}
}
func TestGetModels(t *testing.T) {
vendorsManager := NewVendorsManager()
mockVendor := &MockVendor{name: "testVendor"}
vendorsManager.AddVendors(mockVendor)
models := vendorsManager.GetModels()
if models == nil {
t.Fatalf("GetModels() returned nil")
}
}
func TestHasVendors(t *testing.T) {
vendorsManager := NewVendorsManager()
if vendorsManager.HasVendors() {
t.Fatalf("HasVendors() should return false for an empty manager")
}
mockVendor := &MockVendor{name: "testVendor"}
vendorsManager.AddVendors(mockVendor)
if !vendorsManager.HasVendors() {
t.Fatalf("HasVendors() should return true after adding a vendor")
}
}
func TestFindByName(t *testing.T) {
vendorsManager := NewVendorsManager()
mockVendor := &MockVendor{name: "testVendor"}
vendorsManager.AddVendors(mockVendor)
foundVendor := vendorsManager.FindByName("testVendor")
if foundVendor == nil {
t.Fatalf("FindByName() did not find added vendor")
}
}
func TestReadModels(t *testing.T) {
vendorsManager := NewVendorsManager()
mockVendor := &MockVendor{name: "testVendor"}
vendorsManager.AddVendors(mockVendor)
vendorsManager.readModels()
if vendorsManager.Models == nil || len(vendorsManager.Models.Vendors) == 0 {
t.Fatalf("readModels() did not read models correctly")
}
}
func TestSetup(t *testing.T) {
vendorsManager := NewVendorsManager()
mockVendor := &MockVendor{name: "testVendor"}
vendorsManager.AddVendors(mockVendor)
vendors, err := vendorsManager.Setup()
if err != nil {
t.Fatalf("Setup() error = %v", err)
}
if len(vendors) == 0 {
t.Fatalf("Setup() did not setup any vendors")
}
}
// MockVendor is a mock implementation of the Vendor interface for testing purposes.
type MockVendor struct {
*common.Settings
name string
}
func (o *MockVendor) SendStream(messages []*common.Message, options *common.ChatOptions, strings chan string) error {
// TODO implement me
panic("implement me")
}
func (o *MockVendor) Send(ctx context.Context, messages []*common.Message, options *common.ChatOptions) (string, error) {
// TODO implement me
panic("implement me")
}
func (o *MockVendor) SetupFillEnvFileContent(buffer *bytes.Buffer) {
// TODO implement me
panic("implement me")
}
func (o *MockVendor) IsConfigured() bool {
return false
}
func (o *MockVendor) GetSettings() *common.Settings {
return o.Settings
}
func (o *MockVendor) GetName() string {
return o.name
}
func (o *MockVendor) Configure() error {
return nil
}
func (o *MockVendor) Setup() error {
return nil
}
func (o *MockVendor) ListModels() ([]string, error) {
return []string{"model1", "model2"}, nil
}

View File

@@ -1,21 +0,0 @@
package db
type Contexts struct {
*Storage
}
// GetContext Load a context from file
func (o *Contexts) GetContext(name string) (ret *Context, err error) {
var content []byte
if content, err = o.Load(name); err != nil {
return
}
ret = &Context{Name: name, Content: string(content)}
return
}
type Context struct {
Name string
Content string
}

View File

@@ -1 +0,0 @@
package db

View File

@@ -1,38 +0,0 @@
package db
import (
"fmt"
"github.com/danielmiessler/fabric/common"
)
type Sessions struct {
*Storage
}
func (o *Sessions) GetOrCreateSession(name string) (session *Session, err error) {
session = &Session{Name: name}
if o.Exists(name) {
err = o.LoadAsJson(name, &session.Messages)
} else {
fmt.Printf("Creating new session: %s\n", name)
}
return
}
func (o *Sessions) SaveSession(session *Session) (err error) {
return o.SaveAsJson(session.Name, session.Messages)
}
type Session struct {
Name string
Messages []*common.Message
}
func (o *Session) IsEmpty() bool {
return len(o.Messages) == 0
}
func (o *Session) Append(messages ...*common.Message) {
o.Messages = append(o.Messages, messages...)
}

92
go.mod
View File

@@ -2,74 +2,96 @@ module github.com/danielmiessler/fabric
go 1.22.5
toolchain go1.22.6
require (
github.com/anaskhan96/soup v1.2.5
github.com/atotto/clipboard v0.1.4
github.com/gin-gonic/gin v1.10.0
github.com/go-git/go-git/v5 v5.12.0
github.com/google/generative-ai-go v0.17.0
github.com/go-shiori/go-readability v0.0.0-20240923125239-59a7bd165825
github.com/google/generative-ai-go v0.18.0
github.com/jessevdk/go-flags v1.6.1
github.com/joho/godotenv v1.5.1
github.com/liushuangls/go-anthropic/v2 v2.6.0
github.com/ollama/ollama v0.3.6
github.com/liushuangls/go-anthropic/v2 v2.8.0
github.com/ollama/ollama v0.3.11
github.com/otiai10/copy v1.14.0
github.com/pkg/errors v0.9.1
github.com/samber/lo v1.47.0
github.com/sashabaranov/go-openai v1.28.2
github.com/sashabaranov/go-openai v1.30.0
github.com/stretchr/testify v1.9.0
google.golang.org/api v0.192.0
golang.org/x/text v0.19.0
google.golang.org/api v0.197.0
)
require (
cloud.google.com/go v0.115.0 // indirect
cloud.google.com/go/ai v0.8.0 // indirect
cloud.google.com/go/auth v0.8.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect
cloud.google.com/go/compute/metadata v0.5.0 // indirect
cloud.google.com/go/longrunning v0.5.7 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
cloud.google.com/go v0.115.1 // indirect
cloud.google.com/go/ai v0.8.2 // indirect
cloud.google.com/go/auth v0.9.4 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
cloud.google.com/go/compute/metadata v0.5.1 // indirect
cloud.google.com/go/longrunning v0.6.1 // indirect
dario.cat/mergo v1.0.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/anaskhan96/soup v1.2.5 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudflare/circl v1.4.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/cyphar/filepath-securejoin v0.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
go.opentelemetry.io/otel v1.26.0 // indirect
go.opentelemetry.io/otel/metric v1.26.0 // indirect
go.opentelemetry.io/otel/trace v1.26.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/oauth2 v0.22.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect
go.opentelemetry.io/otel v1.30.0 // indirect
go.opentelemetry.io/otel/metric v1.30.0 // indirect
go.opentelemetry.io/otel/trace v1.30.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/time v0.6.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect
google.golang.org/grpc v1.64.1 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/grpc v1.66.2 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

202
go.sum
View File

@@ -1,41 +1,53 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w=
cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE=
cloud.google.com/go/auth v0.8.1 h1:QZW9FjC5lZzN864p13YxvAtGUlQ+KgRL+8Sg45Z6vxo=
cloud.google.com/go/auth v0.8.1/go.mod h1:qGVp/Y3kDRSDZ5gFD/XPUfYQ9xW1iI7q8RIRoCyBbJc=
cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI=
cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I=
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ=
cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc=
cloud.google.com/go/ai v0.8.2 h1:LEaQwqBv+k2ybrcdTtCTc9OPZXoEdcQaGrfvDYS6Bnk=
cloud.google.com/go/ai v0.8.2/go.mod h1:Wb3EUUGWwB6yHBaUf/+oxUq/6XbCaU1yh0GrwUS8lr4=
cloud.google.com/go/auth v0.9.4 h1:DxF7imbEbiFu9+zdKC6cKBko1e8XeJnipNqIbWZ+kDI=
cloud.google.com/go/auth v0.9.4/go.mod h1:SHia8n6//Ya940F1rLimhJCjjx7KE17t0ctFEci3HkA=
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
cloud.google.com/go/compute/metadata v0.5.1 h1:NM6oZeZNlYjiwYje+sYFjEpP0Q0zCan1bmQW/KmIrGs=
cloud.google.com/go/compute/metadata v0.5.1/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc=
cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/anaskhan96/soup v1.2.5 h1:V/FHiusdTrPrdF4iA1YkVxsOpdNcgvqT1hG+YtcZ5hM=
github.com/anaskhan96/soup v1.2.5/go.mod h1:6YnEp9A2yywlYdM4EgDz9NEHclocMepEtku7wg6Cq3s=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cloudflare/circl v1.4.0 h1:BV7h5MgrktNzytKmWjpOtdYrf0lkkbF8YMlBGPhJQrY=
github.com/cloudflare/circl v1.4.0/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/cyphar/filepath-securejoin v0.3.2 h1:QhZu5AxQ+o1XZH0Ye05YzvJ0kAdK6VQc0z9NNMek7gc=
github.com/cyphar/filepath-securejoin v0.3.2/go.mod h1:F7i41x/9cBF7lzCrVsYs9fuzwRZm4NQsGTBdpp6mETc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -49,6 +61,12 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@@ -64,6 +82,22 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c h1:wpkoddUomPfHiOziHZixGO5ZBS73cKqVzZipfrLmO1w=
github.com/go-shiori/dom v0.0.0-20230515143342-73569d674e1c/go.mod h1:oVDCh3qjJMLVUSILBRwrm+Bc6RNXGZYtoh9xdvf1ffM=
github.com/go-shiori/go-readability v0.0.0-20240923125239-59a7bd165825 h1:CpSi7xiWqGaAqVn/2MsbRoDmPwXMvvQUu3hLjX1QrOM=
github.com/go-shiori/go-readability v0.0.0-20240923125239-59a7bd165825/go.mod h1:YWa00ashoPZMAOElrSn4E1cJErhDVU6PWAll4Hxzn+w=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
@@ -80,8 +114,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/generative-ai-go v0.17.0 h1:kUmCXUIwJouD7I7ev3OmxzzQVICyhIWAxaXk2yblCMY=
github.com/google/generative-ai-go v0.17.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E=
github.com/google/generative-ai-go v0.18.0 h1:6ybg9vOCLcI/UpBBYXOTVgvKmcUKFRNj+2Cj3GnebSo=
github.com/google/generative-ai-go v0.18.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -90,13 +124,14 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@@ -105,8 +140,14 @@ github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bB
github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -114,16 +155,28 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/liushuangls/go-anthropic/v2 v2.6.0 h1:hkgLQPD04wL4lFrV5ZoGlIyy4f6P+brIuRlzn2S8K9s=
github.com/liushuangls/go-anthropic/v2 v2.6.0/go.mod h1:8BKv/fkeTaL5R9R9bGkaknYBueyw2WxY20o7bImbOek=
github.com/ollama/ollama v0.3.6 h1:nA/N0AmjP327po5cZDGLqI40nl+aeei0pD0dLa92ypE=
github.com/ollama/ollama v0.3.6/go.mod h1:YrWoNkFnPOYsnDvsf/Ztb1wxU9/IXrNsQHqcxbY2r94=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/liushuangls/go-anthropic/v2 v2.8.0 h1:0zH2jDNycbrlszxnLrG+Gx8vVT0yJAPWU4s3ZTkWzgI=
github.com/liushuangls/go-anthropic/v2 v2.8.0/go.mod h1:8BKv/fkeTaL5R9R9bGkaknYBueyw2WxY20o7bImbOek=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/ollama/ollama v0.3.11 h1:Fs1B5WjXYUvr5bkMZZpUJfiqIAxrymujRidFABwMeV8=
github.com/ollama/ollama v0.3.11/go.mod h1:YrWoNkFnPOYsnDvsf/Ztb1wxU9/IXrNsQHqcxbY2r94=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -131,59 +184,70 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/sashabaranov/go-openai v1.28.2 h1:Q3pi34SuNYNN7YrqpHlHbpeYlf75ljgHOAVM/r1yun0=
github.com/sashabaranov/go-openai v1.28.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/sashabaranov/go-openai v1.30.0 h1:fHv9urGxABfm885xGWsXFSk5cksa+8dJ4jGli/UQQcI=
github.com/sashabaranov/go-openai v1.30.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc=
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 h1:hCq2hNMwsegUvPzI7sPOvtO9cqyy5GbWt/Ybp2xrx8Q=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0/go.mod h1:LqaApwGx/oUmzsbqxkzuBvyoPpkxk3JQWnqfVrJ3wCA=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI=
go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w=
go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ=
go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc=
go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -198,11 +262,12 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -226,15 +291,17 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -242,8 +309,9 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -254,28 +322,26 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.192.0 h1:PljqpNAfZaaSpS+TnANfnNAXKdzHM/B9bKhwRlo7JP0=
google.golang.org/api v0.192.0/go.mod h1:9VcphjvAxPKLmSxVSzPlSRXy/5ARMEw5bf58WoVXafQ=
google.golang.org/api v0.197.0 h1:x6CwqQLsFiA5JKAiGyGBjc2bNtHtLddhJCE2IKuhhcQ=
google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY=
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf h1:liao9UHurZLtiEwBgT9LMOnKYsHze6eA6w1KQCMVN2Q=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -291,8 +357,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/gookit/color.v1 v1.1.6 h1:5fB10p6AUFjhd2ayq9JgmJWr9WlTrguFdw3qlYtKNHk=
gopkg.in/gookit/color.v1 v1.1.6/go.mod h1:IcEkFGaveVShJ+j8ew+jwe9epHyGpJ9IrptHmW3laVY=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -302,3 +366,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@@ -2,14 +2,15 @@ package main
import (
"fmt"
"github.com/jessevdk/go-flags"
"os"
"github.com/danielmiessler/fabric/cli"
)
func main() {
_, err := cli.Cli()
if err != nil {
err := cli.Cli(version)
if err != nil && !flags.WroteHelp(err) {
fmt.Printf("%s\n", err)
os.Exit(1)
}

View File

@@ -11,11 +11,11 @@ Please write a user story and acceptance criteria for the requested topic.
Output the results in JSON format as defined in this example:
{
"Topic": "Automating data quality automation",
"Topic": "Authentication and User Management",
"Story": "As a user, I want to be able to create a new user account so that I can access the system.",
"Criteria": "Given that I am a user, when I click the 'Create Account' button, then I should be prompted to enter my email address, password, and confirm password. When I click the 'Submit' button, then I should be redirected to the login page."
}
# INPUT:
INPUT:
INPUT:

View File

@@ -0,0 +1,29 @@
# IDENTITY and PURPOSE
You are a military historian and strategic analyst specializing in dissecting historical battles. Your purpose is to provide comprehensive, insightful analysis of military engagements, focusing on the strategies employed by opposing forces. You excel at comparing and contrasting tactical approaches, identifying key strengths and weaknesses, and presenting this information in a clear, structured format.
# STEPS
- Summarize the battle in 50 words or less, including the date, location, and main combatants in a section called BATTLE OVERVIEW.
- Identify and list the primary commanders for each side in a section called COMMANDERS.
- Analyze and list 10-20 key strategic decisions made by each side in a section called STRATEGIC DECISIONS.
- Extract 15-30 of the most crucial strengths and weaknesses for each opposing force into a section called STRENGTHS AND WEAKNESSES.
- Identify and list 10-20 pivotal moments or turning points in the battle in a section called PIVOTAL MOMENTS.
- Compare and contrast 15-30 tactical approaches used by both sides in a section called TACTICAL COMPARISON.
- Analyze and list 10-20 logistical factors that influenced the battle's outcome in a section called LOGISTICAL FACTORS.
- Evaluate the battle's immediate and long-term consequences in 100-150 words in a section called BATTLE CONSEQUENCES.
- Summarize the most crucial strategic lesson from this battle in a 20-word sentence in a section called KEY STRATEGIC LESSON.
# OUTPUT INSTRUCTIONS
- Only output in Markdown format.
- Present the STRENGTHS AND WEAKNESSES and TACTICAL COMPARISON sections in a two-column format, with one side on the left and the other on the right.
- Write the STRATEGIC DECISIONS bullets as exactly 20 words each.
- Write the PIVOTAL MOMENTS bullets as exactly 15 words each.
- Write the LOGISTICAL FACTORS bullets as exactly 15 words each.
- Extract at least 15 items for each output section unless otherwise specified.
- Do not give warnings or notes; only output the requested sections.
- Use bulleted lists for output, not numbered lists.
- Do not repeat information across different sections.
- Ensure variety in how bullet points begin; avoid repetitive phrasing.
- Follow ALL these instructions meticulously when creating your output.
# INPUT
INPUT:

View File

@@ -0,0 +1,39 @@
# IDENTITY
You are an expert at looking at a presentation, an essay, or a full body of lifetime work, and clearly and accurately articulating what the core message is.
# GOAL
- Produce a clear sentence that perfectly articulates the core message as presented in a given text or body of work.
# EXAMPLE
If the input is all of Victor Frankl's work, then the core message would be:
Finding meaning in suffering is key to human resilience, purpose, and enduring lifes challenges.
END EXAMPLE
# STEPS
- Fully digest the input.
- Determine if the input is a single text or a body of work.
- Based on which it is, parse the thing that's supposed to be parsed.
- Extract the core message from the parsed text into a single sentence.
# OUTPUT
- Output a single, 15-word sentence that perfectly articulates the core message as presented in the input.
# OUTPUT INSTRUCTIONS
- The sentence should be a single sentence that is 15 words or fewer, with no special formatting or anything else.
- Do not include any setup to the sentence, e.g., "The core message is to…", etc. Just list the core message and nothing else.
- ONLY OUTPUT THE CORE MESSAGE, not a setup to it, commentary on it, or anything else.
- Do not ask questions or complain in any way about the task.

View File

@@ -0,0 +1,37 @@
# IDENTITY
You are an expert at looking at an input and extracting the most redeeming thing about them, even if they're mostly horrible.
# GOAL
- Produce the most redeeming thing about the thing given in input.
# EXAMPLE
If the body of work is all of Ted Kazcynski's writings, then the most redeeming thing him would be:
He really stuck to his convictions by living in a cabin in the woods.
END EXAMPLE
# STEPS
- Fully digest the input.
- Determine if the input is a single text or a body of work.
- Based on which it is, parse the thing that's supposed to be parsed.
- Extract the most redeeming thing with the world from the parsed text into a single sentence.
# OUTPUT
- Output a single, 15-word sentence that perfectly articulates the most redeeming thing with the world as presented in the input.
# OUTPUT INSTRUCTIONS
- The sentence should be a single sentence that is 15 words or fewer, with no special formatting or anything else.
- Do not include any setup to the sentence, e.g., "The most redeeming thing…", etc. Just list the redeeming thing and nothing else.
- Do not ask questions or complain in any way about the task.

View File

@@ -6,7 +6,7 @@ Take a step back and think step-by-step about how to achieve the best possible r
# STEPS
- Extract all predictions made within the content.
- Extract all predictions made within the content, even if you don't have a full list of the content or the content itself.
- For each prediction, extract the following:

View File

@@ -0,0 +1,39 @@
# IDENTITY
You are an expert at looking at a presentation, an essay, or a full body of lifetime work, and clearly and accurately articulating what the author(s) believe is the primary solution for the world.
# GOAL
- Produce a clear sentence that perfectly articulates the primary solution with the world as presented in a given text or body of work.
# EXAMPLE
If the body of work is all of Ted Kazcynski's writings, then the primary solution with the world would be:
Reject all technology and return to a natural, pre-technological state of living.
END EXAMPLE
# STEPS
- Fully digest the input.
- Determine if the input is a single text or a body of work.
- Based on which it is, parse the thing that's supposed to be parsed.
- Extract the primary solution with the world from the parsed text into a single sentence.
# OUTPUT
- Output a single, 15-word sentence that perfectly articulates the primary solution with the world as presented in the input.
# OUTPUT INSTRUCTIONS
- The sentence should be a single sentence that is 15 words or fewer, with no special formatting or anything else.
- Do not include any setup to the sentence, e.g., "The solution according to…", etc. Just list the problem and nothing else.
- ONLY OUTPUT THE SOLUTION, not a setup to the solution. Or a description of the solution. Just the solution.
- Do not ask questions or complain in any way about the task.

View File

@@ -0,0 +1,31 @@
# IDENTITY and PURPOSE
You extract the list of product features from the input.
Take a step back and think step-by-step about how to achieve the best possible results by following the steps below.
# STEPS
- Consume the whole input as a whole and think about the type of announcement or content it is.
- Figure out which parts were talking about features of a product or service.
- Output the list of features as a bulleted list of 15 words per bullet.
# OUTPUT INSTRUCTIONS
- Only output Markdown.
- Do not give warnings or notes; only output the requested sections.
- You use bulleted lists for output, not numbered lists.
- Do not repeat ideas, quotes, facts, or resources.
- Do not start items with the same opening words.
- Ensure you follow ALL these instructions when creating your output.
# INPUT
INPUT:

View File

@@ -5,7 +5,9 @@ You are an expert at extracting the sponsors and potential sponsors from a given
# Steps
- Consume the whole transcript so you understand what is content, what is meta information, etc.
- Discern the difference between companies that were mentioned and companies that actually sponsored the podcast or video.
- Output the following:
## OFFICIAL SPONSORS
@@ -15,36 +17,20 @@ You are an expert at extracting the sponsors and potential sponsors from a given
- $SOURCE_CHANNEL$ | $SPONSOR3$ | $SPONSOR3_DESCRIPTION$ | $SPONSOR3_LINK$
- And so on…
## POTENTIAL SPONSORS
- $SOURCE_CHANNEL$ | $SPONSOR1$ | $SPONSOR1_DESCRIPTION$ | $SPONSOR1_LINK$
- $SOURCE_CHANNEL$ | $SPONSOR2$ | $SPONSOR2_DESCRIPTION$ | $SPONSOR2_LINK$
- $SOURCE_CHANNEL$ | $SPONSOR3$ | $SPONSOR3_DESCRIPTION$ | $SPONSOR3_LINK$
- And so on…
# EXAMPLE OUTPUT
## OFFICIAL SPONSORS
- AI Jason's YouTube Channel | Flair | Flair is a threat intel platform powered by AI. | https://flair.ai
- Matthew Berman's YouTube Channel | Weaviate | Weviate is an open-source knowledge graph powered by ML. | https://weaviate.com
- Unsupervised Learning Website | JunaAI | JunaAI is a platform for AI-powered content creation. | https://junaai.com
- The AI Junkie Podcast | JunaAI | JunaAI is a platform for AI-powered content creation. | https://junaai.com
## POTENTIAL SPONSORS
- AI Jason's YouTube Channel | Flair | Flair is a threat intel platform powered by AI. | https://flair.ai
- Matthew Berman's YouTube Channel | Weaviate | Weviate is an open-source knowledge graph powered by ML. | https://weaviate.com
- Unsupervised Learning Website | JunaAI | JunaAI is a platform for AI-powered content creation. | https://junaai.com
- The AI Junkie Podcast | JunaAI | JunaAI is a platform for AI-powered content creation. | https://junaai.com
- Flair | Flair is a threat intel platform powered by AI. | https://flair.ai
- Weaviate | Weviate is an open-source knowledge graph powered by ML. | https://weaviate.com
- JunaAI | JunaAI is a platform for AI-powered content creation. | https://junaai.com
- JunaAI | JunaAI is a platform for AI-powered content creation. | https://junaai.com
## END EXAMPLE OUTPUT
# OUTPUT INSTRUCTIONS
- The official sponsor list should only include companies that officially sponsored the content in question.
- The potential sponsor list should include companies that were mentioned during the content but that didn't officially sponsor.
- Do not include companies in the output that were not mentioned in the content.
- Do not output warnings or notes—just the requested sections.
# INPUT:

View File

@@ -0,0 +1,27 @@
#!/bin/bash
# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title Capture Thinkers Work
# @raycast.mode fullOutput
# Optional parameters:
# @raycast.icon 🧠
# @raycast.argument1 { "type": "text", "placeholder": "Input text", "optional": false, "percentEncoded": true}
# Documentation:
# @raycast.description Run fabric capture_thinkers_work on the input text
# @raycast.author Daniel Miessler
# @raycast.authorURL https://github.com/danielmiessler
# Set PATH to include common locations and $HOME/go/bin
PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$HOME/go/bin:$PATH"
# Use the PATH to find and execute fabric
if command -v fabric >/dev/null 2>&1; then
fabric -sp capture_thinkers_work "${1}"
else
echo "Error: fabric command not found in PATH"
echo "Current PATH: $PATH"
exit 1
fi

View File

@@ -0,0 +1,27 @@
#!/bin/bash
# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title Create Story Explanation
# @raycast.mode fullOutput
# Optional parameters:
# @raycast.icon 🧠
# @raycast.argument1 { "type": "text", "placeholder": "Input text", "optional": false, "percentEncoded": true}
# Documentation:
# @raycast.description Run fabric create_story_explanation on the input text
# @raycast.author Daniel Miessler
# @raycast.authorURL https://github.com/danielmiessler
# Set PATH to include common locations and $HOME/go/bin
PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$HOME/go/bin:$PATH"
# Use the PATH to find and execute fabric
if command -v fabric >/dev/null 2>&1; then
fabric -sp create_story_explanation "${1}"
else
echo "Error: fabric command not found in PATH"
echo "Current PATH: $PATH"
exit 1
fi

View File

@@ -0,0 +1,27 @@
#!/bin/bash
# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title Extract Primary Problem
# @raycast.mode fullOutput
# Optional parameters:
# @raycast.icon 🧠
# @raycast.argument1 { "type": "text", "placeholder": "Input text", "optional": false, "percentEncoded": true}
# Documentation:
# @raycast.description Run fabric extract_primary_problem on the input text
# @raycast.author Daniel Miessler
# @raycast.authorURL https://github.com/danielmiessler
# Set PATH to include common locations and $HOME/go/bin
PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$HOME/go/bin:$PATH"
# Use the PATH to find and execute fabric
if command -v fabric >/dev/null 2>&1; then
fabric -sp extract_primary_problem "${1}"
else
echo "Error: fabric command not found in PATH"
echo "Current PATH: $PATH"
exit 1
fi

View File

@@ -1,24 +1,35 @@
# IDENTITY
You are an AI assistant designed to provide detailed, step-by-step responses.
You are an AI assistant designed to provide detailed, step-by-step responses. Your outputs should follow this structure:
# STEPS
1. Begin with a <thinking> section.
2. Inside the thinking section:
a. Briefly analyze the question and outline your approach.
b. Present a clear plan of steps to solve the problem.
c. Use a "Chain of Thought" reasoning process if necessary, breaking down y
3. Include a reflection> section for each idea where you:
a. Review your reasoning.
b. Check for potential errors or oversights.
c. Confirm or adjust your conclusion if necessary.
4. Be sure to close all reflection sections.
5. Close the thinking section with </thinking>.
6. Provide your final answer in an output> section.
Always use these tags in your responses. Be thorough in your explanations, sho
Remember: Both <thinking> and < reflection> MUST be tags and must be closed at
Make sure all tags> are on separate lines with no other text.
- a. Briefly analyze the question and outline your approach.
- b. Present a clear plan of steps to solve the problem.
- c. Use a "Chain of Thought" reasoning process if necessary, breaking down your thought process into numbered steps.
3. Include a <reflection> section for each idea where you:
- a. Review your reasoning.
- b. Check for potential errors or oversights.
- c. Confirm or adjust your conclusion if necessary.
- Be sure to close all reflection sections.
- Close the thinking section with </thinking>.
- Provide your final answer in an <output> section.
Always use these tags in your responses. Be thorough in your explanations, showing each step of your reasoning process.
Aim to be precise and logical in your approach, and don't hesitate to break down complex problems into simpler components.
Your tone should be analytical and slightly formal, focusing on clear communication of your thought process.
Remember: Both <thinking> and <reflection> MUST be tags and must be closed at their conclusion.
Make sure all <tags> are on separate lines with no other text.
# INPUT

View File

@@ -8,32 +8,32 @@ Take a step back and think step-by-step about how to achieve the best possible r
- Fully digest the content provided.
- Extract all actionables agreed within the meeting.
- Extract all actionables agreed upon within the meeting.
- Extract any interesting ideas brought up in the meeting.
- In a section called TITLE, write a 1 to 5 word title for the meeting
- In a section called TITLE, write a 1 to 5 word title for the meeting.
- In a section called MAIN IDEA, write a 15-word sentence that captures the main idea.
- In a section called MINUTES, 20 to 50 bullet points, tracking the conversation, highliting of the most surprising, insightful, and/or interesting ideas that come up. If there are less than 50 then collect all of them. Make sure you extract at least 20.
- In a section called MINUTES, write 20 to 50 bullet points, highlighting of the most surprising, insightful, and/or interesting ideas that come up in the conversation. If there are less than 50 then collect all of them. Make sure you extract at least 20.
- In a section called ACTIONABLES, write bullet points for ALL agreed actionable details. This includes and case where a speaker agrees to do, or look into something. If there is a deadline mentioned, include it here.
- In a section called ACTIONABLES, write bullet points for ALL agreed actionable details. This includes cases where a speaker agrees to do or look into something. If there is a deadline mentioned, include it here.
- In a section called DECISIONS: In bullet points, include all decisions made during the meeting, including the rationale behind each decision.
- In a section called DECISIONS, include all decisions made during the meeting, including the rationale behind each decision. Present them as bullet points.
- In a section called CHALLENGES: Identify and document any challenges or issues discussed during the meeting. Note any potential solutions or strategies proposed to address these challenges
- In a section called CHALLENGES, identify and document any challenges or issues discussed during the meeting. Note any potential solutions or strategies proposed to address these challenges.
- In a section caled NEXT STEPS, Outline the next steps and action plan to be taken after the meeting
- In a section called NEXT STEPS, outline the next steps and actions to be taken after the meeting.
# OUTPUT INSTRUCTIONS
- Only output Markdown.
- Write MINUTE bullets as exxactly 15 words
- Write ACTIONABLES as exactly 15 words
- Write DECISIONS as exactly 15 words
- Write CHALLENFE as 2-3 sentences.
- Write NEXT STEP a 2-3 sentences
- Write MINUTES as exactly 15 words.
- Write ACTIONABLES as exactly 15 words.
- Write DECISIONS as exactly 15 words.
- Write CHALLENGES as 2-3 sentences.
- Write NEXT STEPS as 2-3 sentences.
- Do not give warnings or notes; only output the requested sections.
- Do not repeat ideas, quotes, facts, or resources.
- You use bulleted lists for output, not numbered lists.
@@ -42,4 +42,4 @@ Take a step back and think step-by-step about how to achieve the best possible r
# INPUT
INPUT:
INPUT:

View File

@@ -5,37 +5,45 @@ import (
"errors"
"fmt"
"github.com/danielmiessler/fabric/plugins"
goopenai "github.com/sashabaranov/go-openai"
"github.com/danielmiessler/fabric/common"
"github.com/liushuangls/go-anthropic/v2"
)
const baseUrl = "https://api.anthropic.com/v1"
func NewClient() (ret *Client) {
vendorName := "Anthropic"
ret = &Client{}
ret.Configurable = &common.Configurable{
Label: vendorName,
EnvNamePrefix: common.BuildEnvVariablePrefix(vendorName),
ret.PluginBase = &plugins.PluginBase{
Name: vendorName,
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
ConfigureCustom: ret.configure,
}
ret.ApiKey = ret.Configurable.AddSetupQuestion("API key", true)
ret.ApiBaseURL = ret.AddSetupQuestion("API Base URL", false)
ret.ApiBaseURL.Value = baseUrl
ret.ApiKey = ret.PluginBase.AddSetupQuestion("API key", true)
// we could provide a setup question for the following settings
ret.maxTokens = 4096
ret.defaultRequiredUserMessage = "Hi"
ret.models = []string{
anthropic.ModelClaude3Haiku20240307, anthropic.ModelClaude3Opus20240229,
anthropic.ModelClaude3Opus20240229, anthropic.ModelClaude2Dot0, anthropic.ModelClaude2Dot1,
anthropic.ModelClaudeInstant1Dot2, "claude-3-5-sonnet-20240620",
string(anthropic.ModelClaude3Haiku20240307), string(anthropic.ModelClaude3Opus20240229),
string(anthropic.ModelClaude3Opus20240229), string(anthropic.ModelClaude2Dot0), string(anthropic.ModelClaude2Dot1),
string(anthropic.ModelClaudeInstant1Dot2), "claude-3-5-sonnet-20240620",
}
return
}
type Client struct {
*common.Configurable
ApiKey *common.SetupQuestion
*plugins.PluginBase
ApiBaseURL *plugins.SetupQuestion
ApiKey *plugins.SetupQuestion
maxTokens int
defaultRequiredUserMessage string
@@ -45,7 +53,11 @@ type Client struct {
}
func (an *Client) configure() (err error) {
an.client = anthropic.NewClient(an.ApiKey.Value)
if an.ApiBaseURL.Value != "" {
an.client = anthropic.NewClient(an.ApiKey.Value, anthropic.WithBaseURL(an.ApiBaseURL.Value))
} else {
an.client = anthropic.NewClient(an.ApiKey.Value)
}
return
}
@@ -104,7 +116,7 @@ func (an *Client) buildMessagesRequest(msgs []*common.Message, opts *common.Chat
messages := an.toMessages(msgs)
ret = anthropic.MessagesRequest{
Model: opts.Model,
Model: anthropic.Model(opts.Model),
Temperature: &temperature,
TopP: &topP,
Messages: messages,
@@ -121,10 +133,8 @@ func (an *Client) toMessages(msgs []*common.Message) (ret []anthropic.Message) {
for _, msg := range normalizedMessages {
var message anthropic.Message
switch msg.Role {
case "user":
case goopenai.ChatMessageRoleUser:
message = anthropic.NewUserTextMessage(msg.Content)
case "system":
message = anthropic.NewAssistantTextMessage(msg.Content)
default:
message = anthropic.NewAssistantTextMessage(msg.Content)
}

View File

@@ -1,10 +1,10 @@
package azure
import (
"github.com/danielmiessler/fabric/plugins"
"github.com/danielmiessler/fabric/plugins/ai/openai"
"strings"
"github.com/danielmiessler/fabric/common"
"github.com/danielmiessler/fabric/vendors/openai"
goopenai "github.com/sashabaranov/go-openai"
)
@@ -19,7 +19,7 @@ func NewClient() (ret *Client) {
type Client struct {
*openai.Client
ApiDeployments *common.SetupQuestion
ApiDeployments *plugins.SetupQuestion
apiDeployments []string
}

View File

@@ -4,26 +4,18 @@ import (
"bytes"
"context"
"fmt"
"github.com/danielmiessler/fabric/plugins"
goopenai "github.com/sashabaranov/go-openai"
"github.com/danielmiessler/fabric/common"
)
type Client struct{}
type Client struct {
*plugins.PluginBase
}
func NewClient() *Client {
return &Client{}
}
func (c *Client) GetName() string {
return "DryRun"
}
func (c *Client) IsConfigured() bool {
return true
}
func (c *Client) Configure() error {
return nil
return &Client{PluginBase: &plugins.PluginBase{Name: "DryRun"}}
}
func (c *Client) ListModels() ([]string, error) {
@@ -35,9 +27,11 @@ func (c *Client) SendStream(msgs []*common.Message, opts *common.ChatOptions, ch
for _, msg := range msgs {
switch msg.Role {
case "system":
case goopenai.ChatMessageRoleSystem:
output += fmt.Sprintf("System:\n%s\n\n", msg.Content)
case "user":
case goopenai.ChatMessageRoleAssistant:
output += fmt.Sprintf("Assistant:\n%s\n\n", msg.Content)
case goopenai.ChatMessageRoleUser:
output += fmt.Sprintf("User:\n%s\n\n", msg.Content)
default:
output += fmt.Sprintf("%s:\n%s\n\n", msg.Role, msg.Content)
@@ -56,14 +50,16 @@ func (c *Client) SendStream(msgs []*common.Message, opts *common.ChatOptions, ch
return nil
}
func (c *Client) Send(ctx context.Context, msgs []*common.Message, opts *common.ChatOptions) (string, error) {
func (c *Client) Send(_ context.Context, msgs []*common.Message, opts *common.ChatOptions) (string, error) {
fmt.Println("Dry run: Would send the following request:")
for _, msg := range msgs {
switch msg.Role {
case "system":
case goopenai.ChatMessageRoleSystem:
fmt.Printf("System:\n%s\n\n", msg.Content)
case "user":
case goopenai.ChatMessageRoleAssistant:
fmt.Printf("Assistant:\n%s\n\n", msg.Content)
case goopenai.ChatMessageRoleUser:
fmt.Printf("User:\n%s\n\n", msg.Content)
default:
fmt.Printf("%s:\n%s\n\n", msg.Role, msg.Content)
@@ -84,6 +80,6 @@ func (c *Client) Setup() error {
return nil
}
func (c *Client) SetupFillEnvFileContent(buffer *bytes.Buffer) {
func (c *Client) SetupFillEnvFileContent(_ *bytes.Buffer) {
// No environment variables needed for dry run
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/danielmiessler/fabric/plugins"
"strings"
"github.com/danielmiessler/fabric/common"
@@ -18,19 +19,19 @@ func NewClient() (ret *Client) {
vendorName := "Gemini"
ret = &Client{}
ret.Configurable = &common.Configurable{
Label: vendorName,
EnvNamePrefix: common.BuildEnvVariablePrefix(vendorName),
ret.PluginBase = &plugins.PluginBase{
Name: vendorName,
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
}
ret.ApiKey = ret.Configurable.AddSetupQuestion("API key", true)
ret.ApiKey = ret.PluginBase.AddSetupQuestion("API key", true)
return
}
type Client struct {
*common.Configurable
ApiKey *common.SetupQuestion
*plugins.PluginBase
ApiKey *plugins.SetupQuestion
}
func (o *Client) ListModels() (ret []string, err error) {

View File

@@ -1,7 +1,7 @@
package groq
import (
"github.com/danielmiessler/fabric/vendors/openai"
"github.com/danielmiessler/fabric/plugins/ai/openai"
)
func NewClient() (ret *Client) {

View File

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

13
plugins/ai/models.go Normal file
View File

@@ -0,0 +1,13 @@
package ai
import (
"github.com/danielmiessler/fabric/common"
)
func NewVendorsModels() *VendorsModels {
return &VendorsModels{GroupsItemsSelectorString: common.NewGroupsItemsSelectorString("Available models")}
}
type VendorsModels struct {
*common.GroupsItemsSelectorString
}

33
plugins/ai/models_test.go Normal file
View File

@@ -0,0 +1,33 @@
package ai
import (
"testing"
)
func TestNewVendorsModels(t *testing.T) {
vendors := NewVendorsModels()
if vendors == nil {
t.Fatalf("NewVendorsModels() returned nil")
}
if len(vendors.GroupsItems) != 0 {
t.Fatalf("NewVendorsModels() returned non-empty VendorsModels map")
}
}
func TestFindVendorsByModelFirst(t *testing.T) {
vendors := NewVendorsModels()
vendors.AddGroupItems("vendor1", []string{"model1", "model2"}...)
vendor := vendors.FindGroupsByItemFirst("model1")
if vendor != "vendor1" {
t.Fatalf("FindVendorsByModelFirst() = %v, want %v", vendor, "vendor1")
}
}
func TestFindVendorsByModel(t *testing.T) {
vendors := NewVendorsModels()
vendors.AddGroupItems("vendor1", []string{"model1", "model2"}...)
foundVendors := vendors.FindGroupsByItem("model1")
if len(foundVendors) != 1 || foundVendors[0] != "vendor1" {
t.Fatalf("FindVendorsByModel() = %v, want %v", foundVendors, []string{"vendor1"})
}
}

View File

@@ -3,6 +3,7 @@ package ollama
import (
"context"
"fmt"
"github.com/danielmiessler/fabric/plugins"
"net/http"
"net/url"
"time"
@@ -17,21 +18,21 @@ func NewClient() (ret *Client) {
vendorName := "Ollama"
ret = &Client{}
ret.Configurable = &common.Configurable{
Label: vendorName,
EnvNamePrefix: common.BuildEnvVariablePrefix(vendorName),
ret.PluginBase = &plugins.PluginBase{
Name: vendorName,
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
ConfigureCustom: ret.configure,
}
ret.ApiUrl = ret.Configurable.AddSetupQuestionCustom("API URL", true,
ret.ApiUrl = ret.PluginBase.AddSetupQuestionCustom("API URL", true,
"Enter your Ollama URL (as a reminder, it is usually http://localhost:11434)")
return
}
type Client struct {
*common.Configurable
ApiUrl *common.SetupQuestion
*plugins.PluginBase
ApiUrl *plugins.SetupQuestion
apiUrl *url.URL
client *ollamaapi.Client

View File

@@ -4,7 +4,9 @@ import (
"context"
"errors"
"fmt"
"github.com/danielmiessler/fabric/plugins"
"io"
"log/slog"
"github.com/danielmiessler/fabric/common"
"github.com/samber/lo"
@@ -23,9 +25,9 @@ func NewClientCompatible(vendorName string, defaultBaseUrl string, configureCust
configureCustom = ret.configure
}
ret.Configurable = &common.Configurable{
Label: vendorName,
EnvNamePrefix: common.BuildEnvVariablePrefix(vendorName),
ret.PluginBase = &plugins.PluginBase{
Name: vendorName,
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
ConfigureCustom: configureCustom,
}
@@ -37,9 +39,9 @@ func NewClientCompatible(vendorName string, defaultBaseUrl string, configureCust
}
type Client struct {
*common.Configurable
ApiKey *common.SetupQuestion
ApiBaseURL *common.SetupQuestion
*plugins.PluginBase
ApiKey *plugins.SetupQuestion
ApiBaseURL *plugins.SetupQuestion
ApiClient *openai.Client
}
@@ -82,7 +84,13 @@ func (o *Client) SendStream(
for {
var response openai.ChatCompletionStreamResponse
if response, err = stream.Recv(); err == nil {
channel <- response.Choices[0].Delta.Content
if len(response.Choices) > 0 {
channel <- response.Choices[0].Delta.Content
} else {
channel <- "\n"
close(channel)
break
}
} else if errors.Is(err, io.EOF) {
channel <- "\n"
close(channel)
@@ -103,7 +111,10 @@ func (o *Client) Send(ctx context.Context, msgs []*common.Message, opts *common.
if resp, err = o.ApiClient.CreateChatCompletion(ctx, req); err != nil {
return
}
ret = resp.Choices[0].Message.Content
if len(resp.Choices) > 0 {
ret = resp.Choices[0].Message.Content
slog.Debug("SystemFingerprint: " + resp.SystemFingerprint)
}
return
}
@@ -111,26 +122,35 @@ func (o *Client) buildChatCompletionRequest(
msgs []*common.Message, opts *common.ChatOptions,
) (ret goopenai.ChatCompletionRequest) {
messages := lo.Map(msgs, func(message *common.Message, _ int) goopenai.ChatCompletionMessage {
var role string
switch message.Role {
case "user":
role = goopenai.ChatMessageRoleUser
case "system":
role = goopenai.ChatMessageRoleSystem
default:
role = goopenai.ChatMessageRoleSystem
}
return goopenai.ChatCompletionMessage{Role: role, Content: message.Content}
return goopenai.ChatCompletionMessage{Role: message.Role, Content: message.Content}
})
ret = goopenai.ChatCompletionRequest{
Model: opts.Model,
Temperature: float32(opts.Temperature),
TopP: float32(opts.TopP),
PresencePenalty: float32(opts.PresencePenalty),
FrequencyPenalty: float32(opts.FrequencyPenalty),
Messages: messages,
if opts.Raw {
ret = goopenai.ChatCompletionRequest{
Model: opts.Model,
Messages: messages,
}
} else {
if opts.Seed == 0 {
ret = goopenai.ChatCompletionRequest{
Model: opts.Model,
Temperature: float32(opts.Temperature),
TopP: float32(opts.TopP),
PresencePenalty: float32(opts.PresencePenalty),
FrequencyPenalty: float32(opts.FrequencyPenalty),
Messages: messages,
}
} else {
ret = goopenai.ChatCompletionRequest{
Model: opts.Model,
Temperature: float32(opts.Temperature),
TopP: float32(opts.TopP),
PresencePenalty: float32(opts.PresencePenalty),
FrequencyPenalty: float32(opts.FrequencyPenalty),
Messages: messages,
Seed: &opts.Seed,
}
}
}
return
}

View File

@@ -0,0 +1,102 @@
package openai
import (
"testing"
"github.com/danielmiessler/fabric/common"
"github.com/sashabaranov/go-openai"
goopenai "github.com/sashabaranov/go-openai"
"github.com/stretchr/testify/assert"
)
func TestBuildChatCompletionRequestPinSeed(t *testing.T) {
var msgs []*common.Message
for i := 0; i < 2; i++ {
msgs = append(msgs, &common.Message{
Role: "User",
Content: "My msg",
})
}
opts := &common.ChatOptions{
Temperature: 0.8,
TopP: 0.9,
PresencePenalty: 0.1,
FrequencyPenalty: 0.2,
Raw: false,
Seed: 1,
}
var expectedMessages []openai.ChatCompletionMessage
for i := 0; i < 2; i++ {
expectedMessages = append(expectedMessages,
openai.ChatCompletionMessage{
Role: msgs[i].Role,
Content: msgs[i].Content,
},
)
}
var expectedRequest = goopenai.ChatCompletionRequest{
Model: opts.Model,
Temperature: float32(opts.Temperature),
TopP: float32(opts.TopP),
PresencePenalty: float32(opts.PresencePenalty),
FrequencyPenalty: float32(opts.FrequencyPenalty),
Messages: expectedMessages,
Seed: &opts.Seed,
}
var client = NewClient()
request := client.buildChatCompletionRequest(msgs, opts)
assert.Equal(t, expectedRequest, request)
}
func TestBuildChatCompletionRequestNilSeed(t *testing.T) {
var msgs []*common.Message
for i := 0; i < 2; i++ {
msgs = append(msgs, &common.Message{
Role: "User",
Content: "My msg",
})
}
opts := &common.ChatOptions{
Temperature: 0.8,
TopP: 0.9,
PresencePenalty: 0.1,
FrequencyPenalty: 0.2,
Raw: false,
Seed: 0,
}
var expectedMessages []openai.ChatCompletionMessage
for i := 0; i < 2; i++ {
expectedMessages = append(expectedMessages,
openai.ChatCompletionMessage{
Role: msgs[i].Role,
Content: msgs[i].Content,
},
)
}
var expectedRequest = goopenai.ChatCompletionRequest{
Model: opts.Model,
Temperature: float32(opts.Temperature),
TopP: float32(opts.TopP),
PresencePenalty: float32(opts.PresencePenalty),
FrequencyPenalty: float32(opts.FrequencyPenalty),
Messages: expectedMessages,
Seed: nil,
}
var client = NewClient()
request := client.buildChatCompletionRequest(msgs, opts)
assert.Equal(t, expectedRequest, request)
}

View File

@@ -1,7 +1,7 @@
package openrouter
import (
"github.com/danielmiessler/fabric/vendors/openai"
"github.com/danielmiessler/fabric/plugins/ai/openai"
)
func NewClient() (ret *Client) {

View File

@@ -1,7 +1,7 @@
package siliconcloud
import (
"github.com/danielmiessler/fabric/vendors/openai"
"github.com/danielmiessler/fabric/plugins/ai/openai"
)
func NewClient() (ret *Client) {

View File

@@ -1,19 +1,15 @@
package vendors
package ai
import (
"bytes"
"context"
"github.com/danielmiessler/fabric/plugins"
"github.com/danielmiessler/fabric/common"
)
type Vendor interface {
GetName() string
IsConfigured() bool
Configure() error
plugins.Plugin
ListModels() ([]string, error)
SendStream([]*common.Message, *common.ChatOptions, chan string) error
Send(context.Context, []*common.Message, *common.ChatOptions) (string, error)
Setup() error
SetupFillEnvFileContent(*bytes.Buffer)
}

147
plugins/ai/vendors.go Normal file
View File

@@ -0,0 +1,147 @@
package ai
import (
"bytes"
"context"
"fmt"
"github.com/danielmiessler/fabric/plugins"
"sync"
)
func NewVendorsManager() *VendorsManager {
return &VendorsManager{
Vendors: []Vendor{},
VendorsByName: map[string]Vendor{},
}
}
type VendorsManager struct {
*plugins.PluginBase
Vendors []Vendor
VendorsByName map[string]Vendor
Models *VendorsModels
}
func (o *VendorsManager) AddVendors(vendors ...Vendor) {
for _, vendor := range vendors {
o.VendorsByName[vendor.GetName()] = vendor
o.Vendors = append(o.Vendors, vendor)
}
}
func (o *VendorsManager) SetupFillEnvFileContent(envFileContent *bytes.Buffer) {
for _, vendor := range o.Vendors {
vendor.SetupFillEnvFileContent(envFileContent)
}
}
func (o *VendorsManager) GetModels() (ret *VendorsModels, err error) {
if o.Models == nil {
err = o.readModels()
}
ret = o.Models
return
}
func (o *VendorsManager) Configure() (err error) {
for _, vendor := range o.Vendors {
_ = vendor.Configure()
}
return
}
func (o *VendorsManager) HasVendors() bool {
return len(o.Vendors) > 0
}
func (o *VendorsManager) FindByName(name string) Vendor {
return o.VendorsByName[name]
}
func (o *VendorsManager) readModels() (err error) {
if len(o.Vendors) == 0 {
err = fmt.Errorf("no AI vendors configured to read models from. Please configure at least one AI vendor")
return
}
o.Models = NewVendorsModels()
var wg sync.WaitGroup
resultsChan := make(chan modelResult, len(o.Vendors))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for _, vendor := range o.Vendors {
wg.Add(1)
go o.fetchVendorModels(ctx, &wg, vendor, resultsChan)
}
// Wait for all goroutines to finish
go func() {
wg.Wait()
close(resultsChan)
}()
// Collect results
for result := range resultsChan {
if result.err != nil {
fmt.Println(result.vendorName, result.err)
cancel() // Cancel remaining goroutines if needed
} else {
o.Models.AddGroupItems(result.vendorName, result.models...)
}
}
return
}
func (o *VendorsManager) fetchVendorModels(
ctx context.Context, wg *sync.WaitGroup, vendor Vendor, resultsChan chan<- modelResult) {
defer wg.Done()
models, err := vendor.ListModels()
select {
case <-ctx.Done():
// Context canceled, don't send the result
return
case resultsChan <- modelResult{vendorName: vendor.GetName(), models: models, err: err}:
// Result sent
}
}
func (o *VendorsManager) Setup() (ret map[string]Vendor, err error) {
ret = map[string]Vendor{}
for _, vendor := range o.Vendors {
fmt.Println()
o.setupVendorTo(vendor, ret)
}
return
}
func (o *VendorsManager) SetupVendor(vendorName string, configuredVendors map[string]Vendor) (err error) {
vendor := o.FindByName(vendorName)
if vendor == nil {
err = fmt.Errorf("vendor %s not found", vendorName)
return
}
o.setupVendorTo(vendor, configuredVendors)
return
}
func (o *VendorsManager) setupVendorTo(vendor Vendor, configuredVendors map[string]Vendor) {
if vendorErr := vendor.Setup(); vendorErr == nil {
fmt.Printf("[%v] configured\n", vendor.GetName())
configuredVendors[vendor.GetName()] = vendor
} else {
delete(configuredVendors, vendor.GetName())
fmt.Printf("[%v] skipped\n", vendor.GetName())
}
return
}
type modelResult struct {
vendorName string
models []string
err error
}

13
plugins/db/api.go Normal file
View File

@@ -0,0 +1,13 @@
package db
type Storage[T any] interface {
Configure() (err error)
Get(name string) (ret *T, err error)
GetNames() (ret []string, err error)
Delete(name string) (err error)
Exists(name string) (ret bool)
Rename(oldName, newName string) (err error)
Save(name string, content []byte) (err error)
Load(name string) (ret []byte, err error)
ListNames() (err error)
}

View File

@@ -0,0 +1,32 @@
package fsdb
import "fmt"
type ContextsEntity struct {
*StorageEntity
}
// Get Load a context from file
func (o *ContextsEntity) Get(name string) (ret *Context, err error) {
var content []byte
if content, err = o.Load(name); err != nil {
return
}
ret = &Context{Name: name, Content: string(content)}
return
}
func (o *ContextsEntity) PrintContext(name string) (err error) {
var context *Context
if context, err = o.Get(name); err != nil {
return
}
fmt.Println(context.Content)
return
}
type Context struct {
Name string
Content string
}

View File

@@ -1,4 +1,4 @@
package db
package fsdb
import (
"os"
@@ -8,8 +8,8 @@ import (
func TestContexts_GetContext(t *testing.T) {
dir := t.TempDir()
contexts := &Contexts{
Storage: &Storage{Dir: dir},
contexts := &ContextsEntity{
StorageEntity: &StorageEntity{Dir: dir},
}
contextName := "testContext"
contextPath := filepath.Join(dir, contextName)
@@ -18,7 +18,7 @@ func TestContexts_GetContext(t *testing.T) {
if err != nil {
t.Fatalf("failed to write context file: %v", err)
}
context, err := contexts.GetContext(contextName)
context, err := contexts.Get(contextName)
if err != nil {
t.Fatalf("failed to get context: %v", err)
}

View File

@@ -1,4 +1,4 @@
package db
package fsdb
import (
"fmt"
@@ -14,17 +14,17 @@ func NewDb(dir string) (db *Db) {
db.EnvFilePath = db.FilePath(".env")
db.Patterns = &Patterns{
Storage: &Storage{Label: "Patterns", Dir: db.FilePath("patterns"), ItemIsDir: true},
db.Patterns = &PatternsEntity{
StorageEntity: &StorageEntity{Label: "Patterns", Dir: db.FilePath("patterns"), ItemIsDir: true},
SystemPatternFile: "system.md",
UniquePatternsFilePath: db.FilePath("unique_patterns.txt"),
}
db.Sessions = &Sessions{
&Storage{Label: "Sessions", Dir: db.FilePath("sessions"), FileExtension: ".json"}}
db.Sessions = &SessionsEntity{
&StorageEntity{Label: "Sessions", Dir: db.FilePath("sessions"), FileExtension: ".json"}}
db.Contexts = &Contexts{
&Storage{Label: "Contexts", Dir: db.FilePath("contexts")}}
db.Contexts = &ContextsEntity{
&StorageEntity{Label: "Contexts", Dir: db.FilePath("contexts")}}
return
}
@@ -32,9 +32,9 @@ func NewDb(dir string) (db *Db) {
type Db struct {
Dir string
Patterns *Patterns
Sessions *Sessions
Contexts *Contexts
Patterns *PatternsEntity
Sessions *SessionsEntity
Contexts *ContextsEntity
EnvFilePath string
}

View File

@@ -1,4 +1,4 @@
package db
package fsdb
import (
"os"

View File

@@ -1,4 +1,4 @@
package db
package fsdb
import (
"fmt"
@@ -7,14 +7,13 @@ import (
"strings"
)
type Patterns struct {
*Storage
type PatternsEntity struct {
*StorageEntity
SystemPatternFile string
UniquePatternsFilePath string
}
// GetPattern finds a pattern by name and returns the pattern as an entry or an error
func (o *Patterns) GetPattern(name string, variables map[string]string) (ret *Pattern, err error) {
func (o *PatternsEntity) Get(name string) (ret *Pattern, err error) {
patternPath := filepath.Join(o.Dir, name, o.SystemPatternFile)
var pattern []byte
@@ -23,13 +22,6 @@ func (o *Patterns) GetPattern(name string, variables map[string]string) (ret *Pa
}
patternStr := string(pattern)
if variables != nil && len(variables) > 0 {
for variableName, value := range variables {
patternStr = strings.ReplaceAll(patternStr, variableName, value)
}
}
ret = &Pattern{
Name: name,
Pattern: patternStr,
@@ -37,7 +29,22 @@ func (o *Patterns) GetPattern(name string, variables map[string]string) (ret *Pa
return
}
func (o *Patterns) PrintLatestPatterns(latestNumber int) (err error) {
// GetApplyVariables finds a pattern by name and returns the pattern as an entry or an error
func (o *PatternsEntity) GetApplyVariables(name string, variables map[string]string) (ret *Pattern, err error) {
if ret, err = o.Get(name); err != nil {
return
}
if variables != nil && len(variables) > 0 {
for variableName, value := range variables {
ret.Pattern = strings.ReplaceAll(ret.Pattern, variableName, value)
}
}
return
}
func (o *PatternsEntity) PrintLatestPatterns(latestNumber int) (err error) {
var contents []byte
if contents, err = os.ReadFile(o.UniquePatternsFilePath); err != nil {
err = fmt.Errorf("could not read unique patterns file. Pleas run --updatepatterns (%s)", err)

View File

@@ -0,0 +1 @@
package fsdb

View File

@@ -0,0 +1,88 @@
package fsdb
import (
"fmt"
"github.com/danielmiessler/fabric/common"
)
type SessionsEntity struct {
*StorageEntity
}
func (o *SessionsEntity) Get(name string) (session *Session, err error) {
session = &Session{Name: name}
if o.Exists(name) {
err = o.LoadAsJson(name, &session.Messages)
} else {
fmt.Printf("Creating new session: %s\n", name)
}
return
}
func (o *SessionsEntity) PrintSession(name string) (err error) {
if o.Exists(name) {
var session Session
if err = o.LoadAsJson(name, &session.Messages); err == nil {
fmt.Println(session.String())
}
}
return
}
func (o *SessionsEntity) SaveSession(session *Session) (err error) {
return o.SaveAsJson(session.Name, session.Messages)
}
type Session struct {
Name string
Messages []*common.Message
vendorMessages []*common.Message
}
func (o *Session) IsEmpty() bool {
return len(o.Messages) == 0
}
func (o *Session) Append(messages ...*common.Message) {
if o.vendorMessages != nil {
for _, message := range messages {
o.Messages = append(o.Messages, message)
o.appendVendorMessage(message)
}
} else {
o.Messages = append(o.Messages, messages...)
}
}
func (o *Session) GetVendorMessages() (ret []*common.Message) {
if o.vendorMessages == nil {
o.vendorMessages = []*common.Message{}
for _, message := range o.Messages {
o.appendVendorMessage(message)
}
}
ret = o.vendorMessages
return
}
func (o *Session) appendVendorMessage(message *common.Message) {
if message.Role != common.ChatMessageRoleMeta {
o.vendorMessages = append(o.vendorMessages, message)
}
}
func (o *Session) GetLastMessage() (ret *common.Message) {
if len(o.Messages) > 0 {
ret = o.Messages[len(o.Messages)-1]
}
return
}
func (o *Session) String() (ret string) {
for _, message := range o.Messages {
ret += fmt.Sprintf("\n--- \n[%v]\n\n%v", message.Role, message.Content)
}
return
}

View File

@@ -1,4 +1,4 @@
package db
package fsdb
import (
"testing"
@@ -8,11 +8,11 @@ import (
func TestSessions_GetOrCreateSession(t *testing.T) {
dir := t.TempDir()
sessions := &Sessions{
Storage: &Storage{Dir: dir, FileExtension: ".json"},
sessions := &SessionsEntity{
StorageEntity: &StorageEntity{Dir: dir, FileExtension: ".json"},
}
sessionName := "testSession"
session, err := sessions.GetOrCreateSession(sessionName)
session, err := sessions.Get(sessionName)
if err != nil {
t.Fatalf("failed to get or create session: %v", err)
}
@@ -23,8 +23,8 @@ func TestSessions_GetOrCreateSession(t *testing.T) {
func TestSessions_SaveSession(t *testing.T) {
dir := t.TempDir()
sessions := &Sessions{
Storage: &Storage{Dir: dir, FileExtension: ".json"},
sessions := &SessionsEntity{
StorageEntity: &StorageEntity{Dir: dir, FileExtension: ".json"},
}
sessionName := "testSession"
session := &Session{Name: sessionName, Messages: []*common.Message{{Content: "message1"}}}

View File

@@ -1,22 +1,23 @@
package db
package fsdb
import (
"encoding/json"
"fmt"
"github.com/samber/lo"
"os"
"path/filepath"
"strings"
"github.com/samber/lo"
)
type Storage struct {
type StorageEntity struct {
Label string
Dir string
ItemIsDir bool
FileExtension string
}
func (o *Storage) Configure() (err error) {
func (o *StorageEntity) Configure() (err error) {
if err = os.MkdirAll(o.Dir, os.ModePerm); err != nil {
return
}
@@ -24,7 +25,7 @@ func (o *Storage) Configure() (err error) {
}
// GetNames finds all patterns in the patterns directory and enters the id, name, and pattern into a slice of Entry structs. it returns these entries or an error
func (o *Storage) GetNames() (ret []string, err error) {
func (o *StorageEntity) GetNames() (ret []string, err error) {
var entries []os.DirEntry
if entries, err = os.ReadDir(o.Dir); err != nil {
err = fmt.Errorf("could not read items from directory: %v", err)
@@ -58,7 +59,41 @@ func (o *Storage) GetNames() (ret []string, err error) {
return
}
func (o *Storage) ListNames() (err error) {
func (o *StorageEntity) Delete(name string) (err error) {
if err = os.Remove(o.BuildFilePathByName(name)); err != nil {
err = fmt.Errorf("could not delete %s: %v", name, err)
}
return
}
func (o *StorageEntity) Exists(name string) (ret bool) {
_, err := os.Stat(o.BuildFilePathByName(name))
ret = !os.IsNotExist(err)
return
}
func (o *StorageEntity) Rename(oldName, newName string) (err error) {
if err = os.Rename(o.BuildFilePathByName(oldName), o.BuildFilePathByName(newName)); err != nil {
err = fmt.Errorf("could not rename %s to %s: %v", oldName, newName, err)
}
return
}
func (o *StorageEntity) Save(name string, content []byte) (err error) {
if err = os.WriteFile(o.BuildFilePathByName(name), content, 0644); err != nil {
err = fmt.Errorf("could not save %s: %v", name, err)
}
return
}
func (o *StorageEntity) Load(name string) (ret []byte, err error) {
if ret, err = os.ReadFile(o.BuildFilePathByName(name)); err != nil {
err = fmt.Errorf("could not load %s: %v", name, err)
}
return
}
func (o *StorageEntity) ListNames() (err error) {
var names []string
if names, err = o.GetNames(); err != nil {
return
@@ -69,62 +104,27 @@ func (o *Storage) ListNames() (err error) {
return
}
fmt.Printf("\n%v:\n", o.Label)
for _, item := range names {
fmt.Printf("\t%s\n", item)
fmt.Printf("%s\n", item)
}
return
}
func (o *Storage) BuildFilePathByName(name string) (ret string) {
func (o *StorageEntity) BuildFilePathByName(name string) (ret string) {
ret = o.BuildFilePath(o.buildFileName(name))
return
}
func (o *Storage) BuildFilePath(fileName string) (ret string) {
func (o *StorageEntity) BuildFilePath(fileName string) (ret string) {
ret = filepath.Join(o.Dir, fileName)
return
}
func (o *Storage) buildFileName(name string) string {
func (o *StorageEntity) buildFileName(name string) string {
return fmt.Sprintf("%s%v", name, o.FileExtension)
}
func (o *Storage) Delete(name string) (err error) {
if err = os.Remove(o.BuildFilePathByName(name)); err != nil {
err = fmt.Errorf("could not delete %s: %v", name, err)
}
return
}
func (o *Storage) Exists(name string) (ret bool) {
_, err := os.Stat(o.BuildFilePathByName(name))
ret = !os.IsNotExist(err)
return
}
func (o *Storage) Rename(oldName, newName string) (err error) {
if err = os.Rename(o.BuildFilePathByName(oldName), o.BuildFilePathByName(newName)); err != nil {
err = fmt.Errorf("could not rename %s to %s: %v", oldName, newName, err)
}
return
}
func (o *Storage) Save(name string, content []byte) (err error) {
if err = os.WriteFile(o.BuildFilePathByName(name), content, 0644); err != nil {
err = fmt.Errorf("could not save %s: %v", name, err)
}
return
}
func (o *Storage) Load(name string) (ret []byte, err error) {
if ret, err = os.ReadFile(o.BuildFilePathByName(name)); err != nil {
err = fmt.Errorf("could not load %s: %v", name, err)
}
return
}
func (o *Storage) SaveAsJson(name string, item interface{}) (err error) {
func (o *StorageEntity) SaveAsJson(name string, item interface{}) (err error) {
var jsonString []byte
if jsonString, err = json.Marshal(item); err == nil {
err = o.Save(name, jsonString)
@@ -135,7 +135,7 @@ func (o *Storage) SaveAsJson(name string, item interface{}) (err error) {
return err
}
func (o *Storage) LoadAsJson(name string, item interface{}) (err error) {
func (o *StorageEntity) LoadAsJson(name string, item interface{}) (err error) {
var content []byte
if content, err = o.Load(name); err != nil {
return

View File

@@ -1,4 +1,4 @@
package db
package fsdb
import (
"testing"
@@ -6,7 +6,7 @@ import (
func TestStorage_SaveAndLoad(t *testing.T) {
dir := t.TempDir()
storage := &Storage{Dir: dir}
storage := &StorageEntity{Dir: dir}
name := "test"
content := []byte("test content")
if err := storage.Save(name, content); err != nil {
@@ -23,7 +23,7 @@ func TestStorage_SaveAndLoad(t *testing.T) {
func TestStorage_Exists(t *testing.T) {
dir := t.TempDir()
storage := &Storage{Dir: dir}
storage := &StorageEntity{Dir: dir}
name := "test"
if storage.Exists(name) {
t.Errorf("expected file to not exist")
@@ -38,7 +38,7 @@ func TestStorage_Exists(t *testing.T) {
func TestStorage_Delete(t *testing.T) {
dir := t.TempDir()
storage := &Storage{Dir: dir}
storage := &StorageEntity{Dir: dir}
name := "test"
if err := storage.Save(name, []byte("test content")); err != nil {
t.Fatalf("failed to save content: %v", err)

View File

@@ -1,4 +1,4 @@
package common
package plugins
import (
"bytes"
@@ -9,41 +9,58 @@ import (
const AnswerReset = "reset"
type Configurable struct {
type Plugin interface {
GetName() string
GetSetupDescription() string
IsConfigured() bool
Configure() error
Setup() error
SetupFillEnvFileContent(*bytes.Buffer)
}
type PluginBase struct {
Settings
SetupQuestions
Label string
EnvNamePrefix string
Name string
SetupDescription string
EnvNamePrefix string
ConfigureCustom func() error
}
func (o *Configurable) GetName() string {
return o.Label
func (o *PluginBase) GetName() string {
return o.Name
}
func (o *Configurable) AddSetting(name string, required bool) (ret *Setting) {
func (o *PluginBase) GetSetupDescription() (ret string) {
if ret = o.SetupDescription; ret == "" {
ret = o.GetName()
}
return
}
func (o *PluginBase) AddSetting(name string, required bool) (ret *Setting) {
ret = NewSetting(fmt.Sprintf("%v%v", o.EnvNamePrefix, BuildEnvVariable(name)), required)
o.Settings = append(o.Settings, ret)
return
}
func (o *Configurable) AddSetupQuestion(name string, required bool) (ret *SetupQuestion) {
func (o *PluginBase) AddSetupQuestion(name string, required bool) (ret *SetupQuestion) {
return o.AddSetupQuestionCustom(name, required, "")
}
func (o *Configurable) AddSetupQuestionCustom(name string, required bool, question string) (ret *SetupQuestion) {
func (o *PluginBase) AddSetupQuestionCustom(name string, required bool, question string) (ret *SetupQuestion) {
setting := o.AddSetting(name, required)
ret = &SetupQuestion{Setting: setting, Question: question}
if ret.Question == "" {
ret.Question = fmt.Sprintf("Enter your %v %v", o.Label, strings.ToUpper(name))
ret.Question = fmt.Sprintf("Enter your %v %v", o.Name, strings.ToUpper(name))
}
o.SetupQuestions = append(o.SetupQuestions, ret)
return
}
func (o *Configurable) Configure() (err error) {
func (o *PluginBase) Configure() (err error) {
if err = o.Settings.Configure(); err != nil {
return
}
@@ -54,8 +71,8 @@ func (o *Configurable) Configure() (err error) {
return
}
func (o *Configurable) Setup() (err error) {
if err = o.Ask(o.Label); err != nil {
func (o *PluginBase) Setup() (err error) {
if err = o.Ask(o.Name); err != nil {
return
}
@@ -63,14 +80,14 @@ func (o *Configurable) Setup() (err error) {
return
}
func (o *Configurable) SetupOrSkip() (err error) {
func (o *PluginBase) SetupOrSkip() (err error) {
if err = o.Setup(); err != nil {
fmt.Printf("[%v] skipped\n", o.GetName())
}
return
}
func (o *Configurable) SetupFillEnvFileContent(fileEnvFileContent *bytes.Buffer) {
func (o *PluginBase) SetupFillEnvFileContent(fileEnvFileContent *bytes.Buffer) {
o.Settings.FillEnvFileContent(fileEnvFileContent)
}
@@ -103,8 +120,9 @@ func (o *Setting) IsDefined() bool {
}
func (o *Setting) Configure() error {
if o.Value == "" {
o.Value = os.Getenv(o.EnvVariable)
envValue := os.Getenv(o.EnvVariable)
if envValue != "" {
o.Value = envValue
}
return o.IsValidErr()
}
@@ -125,6 +143,10 @@ func (o *Setting) Print() {
fmt.Printf("%v: %v\n", o.EnvVariable, o.Value)
}
func NewSetupQuestion(question string) *SetupQuestion {
return &SetupQuestion{Setting: &Setting{}, Question: question}
}
type SetupQuestion struct {
*Setting
Question string

View File

@@ -1,4 +1,4 @@
package common
package plugins
import (
"bytes"
@@ -9,9 +9,9 @@ import (
)
func TestConfigurable_AddSetting(t *testing.T) {
conf := &Configurable{
conf := &PluginBase{
Settings: Settings{},
Label: "TestConfigurable",
Name: "TestConfigurable",
EnvNamePrefix: "TEST_",
}
@@ -26,9 +26,9 @@ func TestConfigurable_Configure(t *testing.T) {
EnvVariable: "TEST_SETTING",
Required: true,
}
conf := &Configurable{
conf := &PluginBase{
Settings: Settings{setting},
Label: "TestConfigurable",
Name: "TestConfigurable",
}
_ = os.Setenv("TEST_SETTING", "test_value")
@@ -42,9 +42,9 @@ func TestConfigurable_Setup(t *testing.T) {
EnvVariable: "TEST_SETTING",
Required: false,
}
conf := &Configurable{
conf := &PluginBase{
Settings: Settings{setting},
Label: "TestConfigurable",
Name: "TestConfigurable",
}
err := conf.Setup()

View File

@@ -0,0 +1,25 @@
package converter
import (
"bytes"
"github.com/go-shiori/go-readability"
)
// HtmlReadability Convert HTML input into a clean, readable view
// args
//
// html (string): full data of web page
//
// return
//
// viewContent (string): html main content
// err (error): parser error
func HtmlReadability(html string) (ret string, err error) {
buf := bytes.NewBufferString(html)
var article readability.Article
if article, err = readability.FromReader(buf, nil); err != nil {
return
}
ret = article.TextContent
return
}

View File

@@ -0,0 +1,46 @@
package converter
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestHtmlReadability(t *testing.T) {
tests := []struct {
name string
html string
expected string
}{
{
name: "Empty HTML",
html: "",
expected: "",
},
{
name: "HTML with text",
html: "<p>Hello World</p>",
expected: "Hello World",
},
{
name: "HTML with nested tags",
html: "<div><p>Hello</p><p>World</p></div>",
expected: "HelloWorld",
},
{
name: "HTML missing tags",
html: "<div><p>Hello</p><p>World</div>",
expected: "HelloWorld",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result, err := HtmlReadability(tc.html)
// 验证结果
assert.NoError(t, err)
assert.Equal(t, tc.expected, result)
})
}
}

71
plugins/tools/defaults.go Normal file
View File

@@ -0,0 +1,71 @@
package tools
import (
"fmt"
"strconv"
"github.com/danielmiessler/fabric/plugins"
"github.com/danielmiessler/fabric/plugins/ai"
"github.com/pkg/errors"
)
func NeeDefaults(getVendorsModels func() (*ai.VendorsModels, error)) (ret *Defaults) {
vendorName := "Default"
ret = &Defaults{
PluginBase: &plugins.PluginBase{
Name: vendorName,
SetupDescription: "Default AI Vendor and Model [required]",
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
},
GetVendorsModels: getVendorsModels,
}
ret.Vendor = ret.AddSetting("Vendor", true)
ret.Model = ret.AddSetupQuestionCustom("Model", true,
"Enter the index the name of your default model")
return
}
type Defaults struct {
*plugins.PluginBase
Vendor *plugins.Setting
Model *plugins.SetupQuestion
GetVendorsModels func() (*ai.VendorsModels, error)
}
func (o *Defaults) Setup() (err error) {
var vendorsModels *ai.VendorsModels
if vendorsModels, err = o.GetVendorsModels(); err != nil {
return
}
vendorsModels.Print()
if err = o.Ask(o.Name); err != nil {
return
}
index, parseErr := strconv.Atoi(o.Model.Value)
if parseErr == nil {
if o.Vendor.Value, o.Model.Value, err = vendorsModels.GetGroupAndItemByItemNumber(index); err != nil {
return
}
} else {
o.Vendor.Value = vendorsModels.FindGroupsByItemFirst(o.Model.Value)
}
//verify
vendorNames := vendorsModels.FindGroupsByItem(o.Model.Value)
if len(vendorNames) == 0 {
err = errors.Errorf("You need to chose an available default model.")
return
}
fmt.Println()
o.Vendor.Print()
o.Model.Print()
return
}

View File

@@ -0,0 +1,71 @@
package jina
// see https://jina.ai for more information
import (
"fmt"
"io"
"net/http"
"github.com/danielmiessler/fabric/plugins"
)
type Client struct {
*plugins.PluginBase
ApiKey *plugins.SetupQuestion
}
func NewClient() (ret *Client) {
label := "Jina AI"
ret = &Client{
PluginBase: &plugins.PluginBase{
Name: label,
SetupDescription: "Jina AI Service - to grab a webpage as clean, LLM-friendly text",
EnvNamePrefix: plugins.BuildEnvVariablePrefix(label),
},
}
ret.ApiKey = ret.AddSetupQuestion("API Key", false)
return
}
// ScrapeURL return the main content of a webpage in clean, LLM-friendly text.
func (jc *Client) ScrapeURL(url string) (ret string, err error) {
return jc.request(fmt.Sprintf("https://r.jina.ai/%s", url))
}
func (jc *Client) ScrapeQuestion(question string) (ret string, err error) {
return jc.request(fmt.Sprintf("https://s.jina.ai/%s", question))
}
func (jc *Client) request(requestURL string) (ret string, err error) {
var req *http.Request
if req, err = http.NewRequest("GET", requestURL, nil); err != nil {
err = fmt.Errorf("error creating request: %w", err)
return
}
// if api keys exist, set the header
if jc.ApiKey.Value != "" {
req.Header.Set("Authorization", "Bearer "+jc.ApiKey.Value)
}
client := &http.Client{}
var resp *http.Response
if resp, err = client.Do(req); err != nil {
err = fmt.Errorf("error sending request: %w", err)
return
}
defer resp.Body.Close()
var body []byte
if body, err = io.ReadAll(resp.Body); err != nil {
err = fmt.Errorf("error reading response body: %w", err)
return
}
ret = string(body)
return
}

View File

@@ -0,0 +1,42 @@
package lang
import (
"github.com/danielmiessler/fabric/plugins"
"golang.org/x/text/language"
)
func NewLanguage() (ret *Language) {
label := "Language"
ret = &Language{}
ret.PluginBase = &plugins.PluginBase{
Name: label,
SetupDescription: "Language - Default AI Vendor Output Language",
EnvNamePrefix: plugins.BuildEnvVariablePrefix(label),
ConfigureCustom: ret.configure,
}
ret.DefaultLanguage = ret.PluginBase.AddSetupQuestionCustom("Output", false,
"Enter your default output language (for example: zh_CN)")
return
}
type Language struct {
*plugins.PluginBase
DefaultLanguage *plugins.SetupQuestion
}
func (o *Language) configure() error {
if o.DefaultLanguage.Value != "" {
langTag, err := language.Parse(o.DefaultLanguage.Value)
if err == nil {
o.DefaultLanguage.Value = langTag.String()
} else {
o.DefaultLanguage.Value = ""
}
}
return nil
}

View File

@@ -1,4 +1,4 @@
package core
package tools
import (
"fmt"
@@ -8,8 +8,9 @@ import (
"sort"
"strings"
"github.com/danielmiessler/fabric/common"
"github.com/danielmiessler/fabric/db"
"github.com/danielmiessler/fabric/plugins"
"github.com/danielmiessler/fabric/plugins/db/fsdb"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
@@ -17,16 +18,21 @@ import (
"github.com/otiai10/copy"
)
func NewPatternsLoader(patterns *db.Patterns) (ret *PatternsLoader) {
const DefaultPatternsGitRepoUrl = "https://github.com/danielmiessler/fabric.git"
const DefaultPatternsGitRepoFolder = "patterns"
func NewPatternsLoader(patterns *fsdb.PatternsEntity) (ret *PatternsLoader) {
label := "Patterns Loader"
ret = &PatternsLoader{
Patterns: patterns,
Patterns: patterns,
loadedFilePath: patterns.BuildFilePath("loaded"),
}
ret.Configurable = &common.Configurable{
Label: label,
EnvNamePrefix: common.BuildEnvVariablePrefix(label),
ConfigureCustom: ret.configure,
ret.PluginBase = &plugins.PluginBase{
Name: label,
SetupDescription: "Patterns - Downloads patterns [required]",
EnvNamePrefix: plugins.BuildEnvVariablePrefix(label),
ConfigureCustom: ret.configure,
}
ret.DefaultGitRepoUrl = ret.AddSetupQuestionCustom("Git Repo Url", true,
@@ -41,11 +47,13 @@ func NewPatternsLoader(patterns *db.Patterns) (ret *PatternsLoader) {
}
type PatternsLoader struct {
*common.Configurable
Patterns *db.Patterns
*plugins.PluginBase
Patterns *fsdb.PatternsEntity
DefaultGitRepoUrl *common.SetupQuestion
DefaultFolder *common.SetupQuestion
DefaultGitRepoUrl *plugins.SetupQuestion
DefaultFolder *plugins.SetupQuestion
loadedFilePath string
pathPatternsPrefix string
tempPatternsFolder string
@@ -58,6 +66,27 @@ func (o *PatternsLoader) configure() (err error) {
return
}
func (o *PatternsLoader) IsConfigured() (ret bool) {
ret = o.PluginBase.IsConfigured()
if ret {
if _, err := os.Stat(o.loadedFilePath); os.IsNotExist(err) {
ret = false
}
}
return
}
func (o *PatternsLoader) Setup() (err error) {
if err = o.PluginBase.Setup(); err != nil {
return
}
if err = o.PopulateDB(); err != nil {
return
}
return
}
// PopulateDB downloads patterns from the internet and populates the patterns folder
func (o *PatternsLoader) PopulateDB() (err error) {
fmt.Printf("Downloading patterns and Populating %s..\n", o.Patterns.Dir)
@@ -90,7 +119,7 @@ func (o *PatternsLoader) PersistPatterns() (err error) {
if currentPattern.Name() == newPattern.Name() {
break
}
copy.Copy(filepath.Join(o.Patterns.Dir, newPattern.Name()), filepath.Join(newPatternsFolder, newPattern.Name()))
err = copy.Copy(filepath.Join(o.Patterns.Dir, newPattern.Name()), filepath.Join(newPatternsFolder, newPattern.Name()))
}
}
return
@@ -98,30 +127,26 @@ func (o *PatternsLoader) PersistPatterns() (err error) {
// movePatterns copies the new patterns into the config directory
func (o *PatternsLoader) movePatterns() (err error) {
os.MkdirAll(o.Patterns.Dir, os.ModePerm)
if err = os.MkdirAll(o.Patterns.Dir, os.ModePerm); err != nil {
return
}
patternsDir := o.tempPatternsFolder
if err = o.PersistPatterns(); err != nil {
return
}
copy.Copy(patternsDir, o.Patterns.Dir) // copies the patterns to the config directory
if err = copy.Copy(patternsDir, o.Patterns.Dir); err != nil { // copies the patterns to the config directory
return
}
//create an empty file to indicate that the patterns have been updated if not exists
_, _ = os.Create(o.loadedFilePath)
err = os.RemoveAll(patternsDir)
return
}
// checks if a pattern already exists in the directory
// func DoesPatternExistAlready(name string) (bool, error) {
// entry := db.Entry{
// Label: name,
// }
// _, err := entry.GetByName()
// if err != nil {
// return false, err
// }
// return true, nil
// }
func (o *PatternsLoader) gitCloneAndCopy() (err error) {
// Clones the given repository, creating the remote, the local branches
// and fetching the objects, everything in memory:
@@ -152,10 +177,10 @@ func (o *PatternsLoader) gitCloneAndCopy() (err error) {
return err
}
var changes []db.DirectoryChange
var changes []fsdb.DirectoryChange
// ... iterates over the commits
if err = cIter.ForEach(func(c *object.Commit) (err error) {
// Get the files changed in this commit by comparing with its parents
// GetApplyVariables the files changed in this commit by comparing with its parents
parentIter := c.Parents()
if err = parentIter.ForEach(func(parent *object.Commit) (err error) {
var patch *object.Patch
@@ -167,7 +192,7 @@ func (o *PatternsLoader) gitCloneAndCopy() (err error) {
for _, fileStat := range patch.Stats() {
if strings.HasPrefix(fileStat.Name, o.pathPatternsPrefix) {
dir := filepath.Dir(fileStat.Name)
changes = append(changes, db.DirectoryChange{Dir: dir, Timestamp: c.Committer.When})
changes = append(changes, fsdb.DirectoryChange{Dir: dir, Timestamp: c.Committer.When})
}
}
return
@@ -186,7 +211,9 @@ func (o *PatternsLoader) gitCloneAndCopy() (err error) {
return changes[i].Timestamp.Before(changes[j].Timestamp)
})
o.makeUniqueList(changes)
if err = o.makeUniqueList(changes); err != nil {
return
}
var commit *object.Commit
if commit, err = r.CommitObject(ref.Hash()); err != nil {
@@ -250,7 +277,7 @@ func (o *PatternsLoader) writeBlobToFile(blob *object.Blob, path string) (err er
return
}
func (o *PatternsLoader) makeUniqueList(changes []db.DirectoryChange) {
func (o *PatternsLoader) makeUniqueList(changes []fsdb.DirectoryChange) (err error) {
uniqueItems := make(map[string]bool)
for _, change := range changes {
if strings.TrimSpace(change.Dir) != "" && !strings.Contains(change.Dir, "=>") {
@@ -271,5 +298,6 @@ func (o *PatternsLoader) makeUniqueList(changes []db.DirectoryChange) {
}
joined := strings.Join(finalList, "\n")
os.WriteFile(o.Patterns.UniquePatternsFilePath, []byte(joined), 0o644)
err = os.WriteFile(o.Patterns.UniquePatternsFilePath, []byte(joined), 0o644)
return
}

View File

@@ -5,14 +5,16 @@ import (
"encoding/json"
"flag"
"fmt"
"github.com/anaskhan96/soup"
"github.com/danielmiessler/fabric/common"
"google.golang.org/api/option"
"google.golang.org/api/youtube/v3"
"log"
"net/url"
"regexp"
"strconv"
"strings"
"github.com/anaskhan96/soup"
"github.com/danielmiessler/fabric/plugins"
"google.golang.org/api/option"
"google.golang.org/api/youtube/v3"
)
func NewYouTube() (ret *YouTube) {
@@ -20,9 +22,10 @@ func NewYouTube() (ret *YouTube) {
label := "YouTube"
ret = &YouTube{}
ret.Configurable = &common.Configurable{
Label: label,
EnvNamePrefix: common.BuildEnvVariablePrefix(label),
ret.PluginBase = &plugins.PluginBase{
Name: label,
SetupDescription: label + " - to grab video transcripts and comments",
EnvNamePrefix: plugins.BuildEnvVariablePrefix(label),
}
ret.ApiKey = ret.AddSetupQuestion("API key", true)
@@ -31,8 +34,8 @@ func NewYouTube() (ret *YouTube) {
}
type YouTube struct {
*common.Configurable
ApiKey *common.SetupQuestion
*plugins.PluginBase
ApiKey *plugins.SetupQuestion
service *youtube.Service
}
@@ -61,17 +64,17 @@ func (o *YouTube) GetVideoId(url string) (ret string, err error) {
return
}
func (o *YouTube) GrabTranscriptForUrl(url string) (ret string, err error) {
func (o *YouTube) GrabTranscriptForUrl(url string, language string) (ret string, err error) {
var videoId string
if videoId, err = o.GetVideoId(url); err != nil {
return
}
return o.GrabTranscript(videoId)
return o.GrabTranscript(videoId, language)
}
func (o *YouTube) GrabTranscript(videoId string) (ret string, err error) {
func (o *YouTube) GrabTranscript(videoId string, language string) (ret string, err error) {
var transcript string
if transcript, err = o.GrabTranscriptBase(videoId); err != nil {
if transcript, err = o.GrabTranscriptBase(videoId, language); err != nil {
err = fmt.Errorf("transcript not available. (%v)", err)
return
}
@@ -89,14 +92,14 @@ func (o *YouTube) GrabTranscript(videoId string) (ret string, err error) {
return
}
func (o *YouTube) GrabTranscriptBase(videoId string) (ret string, err error) {
func (o *YouTube) GrabTranscriptBase(videoId string, language string) (ret string, err error) {
if err = o.initService(); err != nil {
return
}
url := "https://www.youtube.com/watch?v=" + videoId
watchUrl := "https://www.youtube.com/watch?v=" + videoId
var resp string
if resp, err = soup.Get(url); err != nil {
if resp, err = soup.Get(watchUrl); err != nil {
return
}
@@ -117,6 +120,16 @@ func (o *YouTube) GrabTranscriptBase(videoId string) (ret string, err error) {
if len(captionTracks) > 0 {
transcriptURL := captionTracks[0].BaseURL
for _, captionTrack := range captionTracks {
parsedUrl, error := url.Parse(captionTrack.BaseURL)
if error != nil {
err = fmt.Errorf("error parsing caption track")
}
parsedUrlParams, _ := url.ParseQuery(parsedUrl.RawQuery)
if parsedUrlParams["lang"][0] == language {
transcriptURL = captionTrack.BaseURL
}
}
ret, err = soup.Get(transcriptURL)
return
}
@@ -212,7 +225,7 @@ func (o *YouTube) Grab(url string, options *Options) (ret *VideoInfo, err error)
}
if options.Transcript {
if ret.Transcript, err = o.GrabTranscript(videoId); err != nil {
if ret.Transcript, err = o.GrabTranscript(videoId, "en"); err != nil {
return
}
}

19
restapi/contexts.go Normal file
View File

@@ -0,0 +1,19 @@
package restapi
import (
"github.com/danielmiessler/fabric/plugins/db/fsdb"
"github.com/gin-gonic/gin"
)
// ContextsHandler defines the handler for contexts-related operations
type ContextsHandler struct {
*StorageHandler[fsdb.Context]
contexts *fsdb.ContextsEntity
}
// NewContextsHandler creates a new ContextsHandler
func NewContextsHandler(r *gin.Engine, contexts *fsdb.ContextsEntity) (ret *ContextsHandler) {
ret = &ContextsHandler{
StorageHandler: NewStorageHandler[fsdb.Context](r, "contexts", contexts), contexts: contexts}
return
}

35
restapi/patterns.go Normal file
View File

@@ -0,0 +1,35 @@
package restapi
import (
"github.com/danielmiessler/fabric/plugins/db/fsdb"
"github.com/gin-gonic/gin"
"net/http"
)
// PatternsHandler defines the handler for patterns-related operations
type PatternsHandler struct {
*StorageHandler[fsdb.Pattern]
patterns *fsdb.PatternsEntity
}
// NewPatternsHandler creates a new PatternsHandler
func NewPatternsHandler(r *gin.Engine, patterns *fsdb.PatternsEntity) (ret *PatternsHandler) {
ret = &PatternsHandler{
StorageHandler: NewStorageHandler[fsdb.Pattern](r, "patterns", patterns), patterns: patterns}
// TODO: Add custom, replacement routes here
//r.GET("/patterns/:name", ret.Get)
return
}
// Get handles the GET /patterns/:name route
func (h *PatternsHandler) Get(c *gin.Context) {
name := c.Param("name")
variables := make(map[string]string) // Assuming variables are passed somehow
pattern, err := h.patterns.GetApplyVariables(name, variables)
if err != nil {
c.JSON(http.StatusInternalServerError, err.Error())
return
}
c.JSON(http.StatusOK, pattern)
}

28
restapi/serve.go Normal file
View File

@@ -0,0 +1,28 @@
package restapi
import (
"github.com/danielmiessler/fabric/core"
"github.com/gin-gonic/gin"
)
func Serve(registry *core.PluginRegistry, address string) (err error) {
r := gin.Default()
// Middleware
r.Use(gin.Logger())
r.Use(gin.Recovery())
// Register routes
fabricDb := registry.Db
NewPatternsHandler(r, fabricDb.Patterns)
NewContextsHandler(r, fabricDb.Contexts)
NewSessionsHandler(r, fabricDb.Sessions)
// Start server
err = r.Run(address)
if err != nil {
return err
}
return
}

19
restapi/sessions.go Normal file
View File

@@ -0,0 +1,19 @@
package restapi
import (
"github.com/danielmiessler/fabric/plugins/db/fsdb"
"github.com/gin-gonic/gin"
)
// SessionsHandler defines the handler for sessions-related operations
type SessionsHandler struct {
*StorageHandler[fsdb.Session]
sessions *fsdb.SessionsEntity
}
// NewSessionsHandler creates a new SessionsHandler
func NewSessionsHandler(r *gin.Engine, sessions *fsdb.SessionsEntity) (ret *SessionsHandler) {
ret = &SessionsHandler{
StorageHandler: NewStorageHandler[fsdb.Session](r, "sessions", sessions), sessions: sessions}
return ret
}

100
restapi/storage.go Normal file
View File

@@ -0,0 +1,100 @@
package restapi
import (
"fmt"
"github.com/danielmiessler/fabric/plugins/db"
"github.com/gin-gonic/gin"
"io"
"net/http"
)
// StorageHandler defines the handler for storage-related operations
type StorageHandler[T any] struct {
storage db.Storage[T]
}
// NewStorageHandler creates a new StorageHandler
func NewStorageHandler[T any](r *gin.Engine, entityType string, storage db.Storage[T]) (ret *StorageHandler[T]) {
ret = &StorageHandler[T]{storage: storage}
r.GET(fmt.Sprintf("/%s/:name", entityType), ret.Get)
r.GET(fmt.Sprintf("/%s/names", entityType), ret.GetNames)
r.DELETE(fmt.Sprintf("/%s/:name", entityType), ret.Delete)
r.GET(fmt.Sprintf("/%s/exists/:name", entityType), ret.Exists)
r.PUT(fmt.Sprintf("/%s/rename/:oldName/:newName", entityType), ret.Rename)
r.POST(fmt.Sprintf("/%s/:name", entityType), ret.Save)
return
}
// Get handles the GET /storage/:name route
func (h *StorageHandler[T]) Get(c *gin.Context) {
name := c.Param("name")
item, err := h.storage.Get(name)
if err != nil {
c.JSON(http.StatusInternalServerError, err.Error())
return
}
c.JSON(http.StatusOK, item)
}
// GetNames handles the GET /storage/names route
func (h *StorageHandler[T]) GetNames(c *gin.Context) {
names, err := h.storage.GetNames()
if err != nil {
c.JSON(http.StatusInternalServerError, err.Error())
return
}
c.JSON(http.StatusOK, names)
}
// Delete handles the DELETE /storage/:name route
func (h *StorageHandler[T]) Delete(c *gin.Context) {
name := c.Param("name")
err := h.storage.Delete(name)
if err != nil {
c.JSON(http.StatusInternalServerError, err.Error())
return
}
c.Status(http.StatusOK)
}
// Exists handles the GET /storage/exists/:name route
func (h *StorageHandler[T]) Exists(c *gin.Context) {
name := c.Param("name")
exists := h.storage.Exists(name)
c.JSON(http.StatusOK, exists)
}
// Rename handles the PUT /storage/rename/:oldName/:newName route
func (h *StorageHandler[T]) Rename(c *gin.Context) {
oldName := c.Param("oldName")
newName := c.Param("newName")
err := h.storage.Rename(oldName, newName)
if err != nil {
c.JSON(http.StatusInternalServerError, err.Error())
return
}
c.Status(http.StatusOK)
}
// Save handles the POST /storage/save/:name route
func (h *StorageHandler[T]) Save(c *gin.Context) {
name := c.Param("name")
// Read the request body
body := c.Request.Body
defer body.Close()
content, err := io.ReadAll(body)
if err != nil {
c.JSON(http.StatusInternalServerError, err.Error())
return
}
// Save the content to storage
err = h.storage.Save(name, content)
if err != nil {
c.JSON(http.StatusInternalServerError, err.Error())
return
}
c.Status(http.StatusOK)
}

112
setup_fabric.bat Normal file
View File

@@ -0,0 +1,112 @@
@echo off
setlocal enabledelayedexpansion
:: Check if running with administrator privileges
net session >nul 2>&1
if %errorlevel% neq 0 (
echo Please run this script as an administrator.
pause
exit /b 1
)
:: Install Chocolatey (package manager for Windows)
if not exist "%ProgramData%\chocolatey\bin\choco.exe" (
echo Installing Chocolatey...
@"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "[System.Net.ServicePointManager]::SecurityProtocol = 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin"
)
:: Install Go
where go >nul 2>&1
if %errorlevel% neq 0 (
echo Installing Go...
choco install golang -y
set "PATH=%PATH%;C:\Program Files\Go\bin"
)
:: Install Git
where git >nul 2>&1
if %errorlevel% neq 0 (
echo Installing Git...
choco install git -y
)
:: Refresh environment variables
call refreshenv
:: Install Fabric
echo Installing Fabric...
go install github.com/danielmiessler/fabric@latest
:: Run Fabric setup
echo Running Fabric setup...
fabric --setup
:: Install yt helper
echo Installing yt helper...
go install github.com/danielmiessler/yt@latest
:: Prompt user for YouTube API Key
set /p YOUTUBE_API_KEY=Enter your YouTube API Key (press Enter to skip):
if not "!YOUTUBE_API_KEY!"=="" (
echo YOUTUBE_API_KEY=!YOUTUBE_API_KEY!>> %USERPROFILE%\.config\fabric\.env
)
:: Prompt user for OpenAI API Key
set /p OPENAI_API_KEY=Enter your OpenAI API Key (press Enter to skip):
if not "!OPENAI_API_KEY!"=="" (
echo OPENAI_API_KEY=!OPENAI_API_KEY!>> %USERPROFILE%\.config\fabric\.env
)
:: Run Fabric
:run_fabric
cls
echo Fabric is now installed and ready to use.
echo.
echo Available options:
echo 1. Run Fabric with custom options
echo 2. List patterns
echo 3. List models
echo 4. Update patterns
echo 5. Exit
echo.
set /p CHOICE=Enter your choice (1-5):
if "%CHOICE%"=="1" (
set /p PATTERN=Enter pattern (or press Enter to skip):
set /p CONTEXT=Enter context (or press Enter to skip):
set /p SESSION=Enter session (or press Enter to skip):
set /p MODEL=Enter model (or press Enter to skip):
set /p TEMPERATURE=Enter temperature (or press Enter for default):
set /p STREAM=Do you want to stream output? (Y/N):
set "FABRIC_CMD=fabric"
if not "!PATTERN!"=="" set "FABRIC_CMD=!FABRIC_CMD! --pattern !PATTERN!"
if not "!CONTEXT!"=="" set "FABRIC_CMD=!FABRIC_CMD! --context !CONTEXT!"
if not "!SESSION!"=="" set "FABRIC_CMD=!FABRIC_CMD! --session !SESSION!"
if not "!MODEL!"=="" set "FABRIC_CMD=!FABRIC_CMD! --model !MODEL!"
if not "!TEMPERATURE!"=="" set "FABRIC_CMD=!FABRIC_CMD! --temperature !TEMPERATURE!"
if /i "!STREAM!"=="Y" set "FABRIC_CMD=!FABRIC_CMD! --stream"
echo Running Fabric with command: !FABRIC_CMD!
!FABRIC_CMD!
pause
goto run_fabric
) else if "%CHOICE%"=="2" (
fabric --listpatterns
pause
goto run_fabric
) else if "%CHOICE%"=="3" (
fabric --listmodels
pause
goto run_fabric
) else if "%CHOICE%"=="4" (
fabric --updatepatterns
pause
goto run_fabric
) else if "%CHOICE%"=="5" (
exit /b 0
) else (
echo Invalid choice. Please try again.
pause
goto run_fabric
)

3
version.go Normal file
View File

@@ -0,0 +1,3 @@
package main
var version = "v1.4.67"