llms-py 3.0.23__tar.gz → 3.0.25__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {llms_py-3.0.23/llms_py.egg-info → llms_py-3.0.25}/PKG-INFO +1 -1
- llms_py-3.0.25/llms/extensions/github_auth/README.md +169 -0
- llms_py-3.0.25/llms/extensions/github_auth/__init__.py +254 -0
- llms_py-3.0.25/llms/extensions/github_auth/ui/index.mjs +66 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/README.md +13 -6
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/__init__.py +21 -16
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/ui/index.mjs +15 -8
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/llms.json +0 -9
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/main.py +87 -294
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/App.mjs +4 -1
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/ai.mjs +1 -2
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/modules/chat/ChatBody.mjs +1 -2
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/modules/layout.mjs +1 -57
- {llms_py-3.0.23 → llms_py-3.0.25/llms_py.egg-info}/PKG-INFO +1 -1
- {llms_py-3.0.23 → llms_py-3.0.25}/llms_py.egg-info/SOURCES.txt +3 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/pyproject.toml +1 -1
- {llms_py-3.0.23 → llms_py-3.0.25}/setup.py +1 -1
- {llms_py-3.0.23 → llms_py-3.0.25}/LICENSE +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/MANIFEST.in +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/README.md +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/__init__.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/__main__.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/db.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/analytics/ui/index.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/app/README.md +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/app/__init__.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/app/db.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/app/ui/Recents.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/app/ui/index.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/app/ui/threadStore.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/computer/README.md +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/computer/__init__.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/computer/base.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/computer/bash.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/computer/computer.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/computer/edit.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/computer/filesystem.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/computer/platform.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/computer/run.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/CALCULATOR.md +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/__init__.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/codemirror.css +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/codemirror.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/doc/docs.css +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/mode/python/python.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/theme/dracula.css +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/codemirror/theme/mocha.css +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/core_tools/ui/index.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/gallery/README.md +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/gallery/__init__.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/gallery/db.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/gallery/ui/index.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/README.md +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/__init__.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/README.md +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/contrib/auto-render.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/contrib/auto-render.min.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/contrib/auto-render.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/contrib/copy-tex.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/contrib/copy-tex.min.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/contrib/copy-tex.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/contrib/mathtex-script-type.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/contrib/mhchem.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/contrib/mhchem.min.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/contrib/mhchem.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/contrib/render-a11y-string.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/contrib/render-a11y-string.min.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/contrib/render-a11y-string.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/index.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/katex-swap.css +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/katex-swap.min.css +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/katex.css +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/katex.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/katex.min.css +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/katex.min.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/katex.min.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/katex/ui/katex.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/providers/__init__.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/providers/anthropic.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/providers/cerebras.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/providers/chutes.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/providers/google.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/providers/nvidia.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/providers/openai.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/providers/openrouter.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/providers/zai.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/LICENSE +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/errors.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/installer.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/models.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/parser.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/ui/data/skills-top-5000.json +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/ui/skills/create-plan/SKILL.md +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/ui/skills/skill-creator/LICENSE.txt +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/ui/skills/skill-creator/SKILL.md +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/ui/skills/skill-creator/references/output-patterns.md +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/ui/skills/skill-creator/references/workflows.md +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/ui/skills/skill-creator/scripts/init_skill.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/ui/skills/skill-creator/scripts/package_skill.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/ui/skills/skill-creator/scripts/quick_validate.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/skills/validator.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/system_prompts/README.md +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/system_prompts/__init__.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/system_prompts/ui/index.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/system_prompts/ui/prompts.json +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/tools/__init__.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/extensions/tools/ui/index.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/index.html +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/providers-extra.json +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/providers.json +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/app.css +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/ctx.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/fav.svg +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/index.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/lib/chart.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/lib/charts.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/lib/color.js +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/lib/highlight.min.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/lib/idb.min.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/lib/marked.min.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/lib/servicestack-client.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/lib/servicestack-vue.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/lib/vue-router.min.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/lib/vue.min.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/lib/vue.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/markdown.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/modules/chat/SettingsDialog.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/modules/chat/index.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/modules/icons.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/modules/model-selector.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/tailwind.input.css +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/typography.css +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms/ui/utils.mjs +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms_py.egg-info/dependency_links.txt +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms_py.egg-info/entry_points.txt +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms_py.egg-info/not-zip-safe +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms_py.egg-info/requires.txt +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/llms_py.egg-info/top_level.txt +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/requirements.txt +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/setup.cfg +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/tests/test_async.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/tests/test_config.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/tests/test_core_tools_direct.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/tests/test_extensions.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/tests/test_gemini_tool_calling.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/tests/test_gemini_upload.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/tests/test_integration.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/tests/test_interleaved_thinking.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/tests/test_provider_checks.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/tests/test_provider_config.py +0 -0
- {llms_py-3.0.23 → llms_py-3.0.25}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: llms-py
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.25
|
|
4
4
|
Summary: A lightweight CLI tool and OpenAI-compatible server for querying multiple Large Language Model (LLM) providers
|
|
5
5
|
Home-page: https://github.com/ServiceStack/llms
|
|
6
6
|
Author: ServiceStack
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# GitHub Auth Extension
|
|
2
|
+
|
|
3
|
+
The GitHub Auth extension enables OAuth 2.0 authentication via GitHub for your llms application. When enabled, users must sign in with their GitHub account before accessing the application.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **GitHub OAuth 2.0** - Standard OAuth flow with CSRF protection
|
|
8
|
+
- **User Restrictions** - Optionally restrict access to specific GitHub users
|
|
9
|
+
- **Session Management** - Automatic session handling with 24-hour expiry
|
|
10
|
+
- **Environment Variables** - Credentials can use env vars for secure deployment
|
|
11
|
+
|
|
12
|
+
## Configuration
|
|
13
|
+
|
|
14
|
+
Create a config file at `~/.llms/users/default/github_auth/config.json`:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"enabled": true,
|
|
19
|
+
"client_id": "$GITHUB_CLIENT_ID",
|
|
20
|
+
"client_secret": "$GITHUB_CLIENT_SECRET",
|
|
21
|
+
"redirect_uri": "http://localhost:8000/auth/github/callback",
|
|
22
|
+
"restrict_to": "$GITHUB_USERS"
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
| Property | Description |
|
|
27
|
+
|-----------------|-------------|
|
|
28
|
+
| `client_id` | GitHub OAuth App client ID |
|
|
29
|
+
| `client_secret` | GitHub OAuth App client secret |
|
|
30
|
+
| `redirect_uri` | Callback URL registered with GitHub |
|
|
31
|
+
| `restrict_to` | Optional comma/space-delimited list of allowed GitHub usernames |
|
|
32
|
+
| `enabled` | Set to `false` to disable the extension |
|
|
33
|
+
|
|
34
|
+
Values prefixed with `$` are resolved from environment variables.
|
|
35
|
+
|
|
36
|
+
## Creating a GitHub OAuth App
|
|
37
|
+
|
|
38
|
+
1. Go to [GitHub Developer Settings](https://github.com/settings/developers)
|
|
39
|
+
2. Click **New OAuth App**
|
|
40
|
+
3. Fill in the application details:
|
|
41
|
+
- **Application name**: Your app name
|
|
42
|
+
- **Homepage URL**: Your app's homepage (e.g., `http://localhost:8000`)
|
|
43
|
+
- **Authorization callback URL**: Must match your `redirect_uri` (e.g., `http://localhost:8000/auth/github/callback`)
|
|
44
|
+
4. Click **Register application**
|
|
45
|
+
5. Copy the **Client ID** and generate a **Client Secret**
|
|
46
|
+
|
|
47
|
+
## API Endpoints
|
|
48
|
+
|
|
49
|
+
The extension registers these routes:
|
|
50
|
+
|
|
51
|
+
| Method | Endpoint | Description |
|
|
52
|
+
|--------|-------------------------|-------------|
|
|
53
|
+
| GET | `/auth` | Check authentication status |
|
|
54
|
+
| GET | `/auth/github` | Initiate GitHub OAuth flow |
|
|
55
|
+
| GET | `/auth/github/callback` | OAuth callback handler |
|
|
56
|
+
| GET | `/auth/session` | Get current session info |
|
|
57
|
+
| POST | `/auth/logout` | End the current session |
|
|
58
|
+
|
|
59
|
+
### GET /auth
|
|
60
|
+
|
|
61
|
+
Returns the authenticated user's info or a 401 error:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"userId": "12345",
|
|
66
|
+
"userName": "octocat",
|
|
67
|
+
"displayName": "The Octocat",
|
|
68
|
+
"profileUrl": "https://avatars.githubusercontent.com/u/12345",
|
|
69
|
+
"authProvider": "github"
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### GET /auth/session
|
|
74
|
+
|
|
75
|
+
Returns full session details including the session token:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"userId": "12345",
|
|
80
|
+
"userName": "octocat",
|
|
81
|
+
"displayName": "The Octocat",
|
|
82
|
+
"profileUrl": "https://avatars.githubusercontent.com/u/12345",
|
|
83
|
+
"email": "octocat@github.com",
|
|
84
|
+
"created": 1706600000.123,
|
|
85
|
+
"sessionToken": "..."
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## OAuth Flow
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
┌─────────┐ ┌─────────┐ ┌────────┐
|
|
93
|
+
│ Browser │ │ llms │ │ GitHub │
|
|
94
|
+
└────┬────┘ └────┬────┘ └───┬────┘
|
|
95
|
+
│ │ │
|
|
96
|
+
│ GET /auth/github │ │
|
|
97
|
+
├───────────────────►│ │
|
|
98
|
+
│ │ │
|
|
99
|
+
│ 302 Redirect │ │
|
|
100
|
+
│◄───────────────────┤ │
|
|
101
|
+
│ │ │
|
|
102
|
+
│ /login/oauth/authorize?... │
|
|
103
|
+
├────────────────────────────────────────►
|
|
104
|
+
│ │ │
|
|
105
|
+
│ User grants access │
|
|
106
|
+
│◄────────────────────────────────────────
|
|
107
|
+
│ │ │
|
|
108
|
+
│ GET /auth/github/callback?code=... │
|
|
109
|
+
├───────────────────►│ │
|
|
110
|
+
│ │ │
|
|
111
|
+
│ │ POST /access_token │
|
|
112
|
+
│ ├──────────────────►│
|
|
113
|
+
│ │ │
|
|
114
|
+
│ │ access_token │
|
|
115
|
+
│ │◄──────────────────┤
|
|
116
|
+
│ │ │
|
|
117
|
+
│ │ GET /user │
|
|
118
|
+
│ ├──────────────────►│
|
|
119
|
+
│ │ │
|
|
120
|
+
│ │ user info │
|
|
121
|
+
│ │◄──────────────────┤
|
|
122
|
+
│ │ │
|
|
123
|
+
│ 302 /?session=... │ │
|
|
124
|
+
│ Set-Cookie: token │ │
|
|
125
|
+
│◄───────────────────┤ │
|
|
126
|
+
│ │ │
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
1. User clicks "Sign in with GitHub" → redirects to `/auth/github`
|
|
130
|
+
2. Server generates CSRF state token and redirects to GitHub
|
|
131
|
+
3. User authorizes the app on GitHub
|
|
132
|
+
4. GitHub redirects back with authorization code
|
|
133
|
+
5. Server exchanges code for access token
|
|
134
|
+
6. Server fetches user info from GitHub API
|
|
135
|
+
7. Server creates session and sets cookie
|
|
136
|
+
|
|
137
|
+
## Restricting Access
|
|
138
|
+
|
|
139
|
+
To limit access to specific GitHub users, set `restrict_to` in your config:
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"client_id": "...",
|
|
144
|
+
"client_secret": "...",
|
|
145
|
+
"redirect_uri": "...",
|
|
146
|
+
"restrict_to": "alice bob charlie"
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Users not in this list receive a `403 Forbidden` response.
|
|
151
|
+
|
|
152
|
+
## UI Component
|
|
153
|
+
|
|
154
|
+
The extension provides a custom `SignIn` component that displays a "Sign in with GitHub" button. This component automatically overrides the default sign-in UI when the extension is loaded.
|
|
155
|
+
|
|
156
|
+
## Session Storage
|
|
157
|
+
|
|
158
|
+
Sessions are stored in memory with:
|
|
159
|
+
- **Token**: Cryptographically secure random string
|
|
160
|
+
- **User data**: GitHub user ID, username, display name, avatar URL, email
|
|
161
|
+
- **Expiry**: Automatic cleanup after 24 hours
|
|
162
|
+
- **Cookie**: `llms-token` with `httponly` flag for security
|
|
163
|
+
|
|
164
|
+
## Security Notes
|
|
165
|
+
|
|
166
|
+
- **CSRF Protection**: OAuth state tokens prevent cross-site request forgery
|
|
167
|
+
- **State Cleanup**: Expired state tokens (>10 min) are automatically removed
|
|
168
|
+
- **Session Cleanup**: Sessions older than 24 hours are pruned
|
|
169
|
+
- **HttpOnly Cookie**: Session token is not accessible via JavaScript
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import secrets
|
|
5
|
+
import time
|
|
6
|
+
from urllib.parse import parse_qs, urlencode
|
|
7
|
+
|
|
8
|
+
import aiohttp
|
|
9
|
+
from aiohttp import web
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def install(ctx):
|
|
13
|
+
g_app = ctx.app
|
|
14
|
+
|
|
15
|
+
auth_config_file = os.path.join(ctx.get_user_path(), "github_auth", "config.json")
|
|
16
|
+
|
|
17
|
+
auth_config = None
|
|
18
|
+
if os.path.exists(auth_config_file):
|
|
19
|
+
try:
|
|
20
|
+
with open(auth_config_file, encoding="utf-8") as f:
|
|
21
|
+
auth_config = json.load(f)
|
|
22
|
+
if "enabled" in auth_config and not auth_config["enabled"]:
|
|
23
|
+
ctx.log("GitHub Auth is disabled in config")
|
|
24
|
+
auth_config = None
|
|
25
|
+
except Exception as e:
|
|
26
|
+
ctx.err("Failed to load GitHub auth config", e)
|
|
27
|
+
else:
|
|
28
|
+
ctx.dbg(f"GitHub Auth config file '{auth_config_file}' not found")
|
|
29
|
+
|
|
30
|
+
if not auth_config:
|
|
31
|
+
# don't load extension if auth_config is not found or is disabled
|
|
32
|
+
ctx.disabled = True
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
client_id = auth_config.get("client_id", "")
|
|
36
|
+
client_secret = auth_config.get("client_secret", "")
|
|
37
|
+
redirect_uri = auth_config.get("redirect_uri", "")
|
|
38
|
+
restrict_to = auth_config.get("restrict_to", "")
|
|
39
|
+
|
|
40
|
+
# Expand environment variables
|
|
41
|
+
if client_id.startswith("$"):
|
|
42
|
+
client_id = client_id[1:]
|
|
43
|
+
if client_secret.startswith("$"):
|
|
44
|
+
client_secret = client_secret[1:]
|
|
45
|
+
client_secret = os.getenv(client_secret)
|
|
46
|
+
if redirect_uri.startswith("$"):
|
|
47
|
+
redirect_uri = redirect_uri[1:]
|
|
48
|
+
redirect_uri = os.getenv(redirect_uri)
|
|
49
|
+
if restrict_to.startswith("$"):
|
|
50
|
+
restrict_to = restrict_to[1:]
|
|
51
|
+
restrict_to = os.getenv(restrict_to)
|
|
52
|
+
|
|
53
|
+
# check if client_id is set
|
|
54
|
+
if client_id == "GITHUB_CLIENT_ID":
|
|
55
|
+
client_id = os.getenv(client_id)
|
|
56
|
+
if client_secret == "GITHUB_CLIENT_SECRET":
|
|
57
|
+
client_secret = os.getenv(client_secret)
|
|
58
|
+
if restrict_to == "GITHUB_USERS":
|
|
59
|
+
restrict_to = os.getenv(restrict_to)
|
|
60
|
+
|
|
61
|
+
if not client_id or not redirect_uri or not client_secret:
|
|
62
|
+
ctx.disabled = True
|
|
63
|
+
ctx.log("GitHub OAuth client_id, client_secret and redirect_uri are not configured")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
from llms.main import AuthProvider
|
|
67
|
+
|
|
68
|
+
class GitHubAuthProvider(AuthProvider):
|
|
69
|
+
def __init__(self, app):
|
|
70
|
+
super().__init__(app)
|
|
71
|
+
|
|
72
|
+
# Adding an Auth Provider forces Authentication to be enabled
|
|
73
|
+
auth_provider = GitHubAuthProvider(g_app)
|
|
74
|
+
g_app.auth_providers.append(auth_provider)
|
|
75
|
+
|
|
76
|
+
# OAuth handlers
|
|
77
|
+
async def github_auth_handler(request):
|
|
78
|
+
# Generate CSRF state token
|
|
79
|
+
state = secrets.token_urlsafe(32)
|
|
80
|
+
ctx.oauth_states[state] = {"created": time.time(), "redirect_uri": redirect_uri}
|
|
81
|
+
|
|
82
|
+
# Clean up old states (older than 10 minutes)
|
|
83
|
+
current_time = time.time()
|
|
84
|
+
expired_states = [s for s, data in ctx.oauth_states.items() if current_time - data["created"] > 600]
|
|
85
|
+
for s in expired_states:
|
|
86
|
+
del ctx.oauth_states[s]
|
|
87
|
+
|
|
88
|
+
# Build GitHub authorization URL
|
|
89
|
+
params = {
|
|
90
|
+
"client_id": client_id,
|
|
91
|
+
"redirect_uri": redirect_uri,
|
|
92
|
+
"state": state,
|
|
93
|
+
"scope": "read:user user:email",
|
|
94
|
+
}
|
|
95
|
+
auth_url = f"https://github.com/login/oauth/authorize?{urlencode(params)}"
|
|
96
|
+
|
|
97
|
+
return web.HTTPFound(auth_url)
|
|
98
|
+
|
|
99
|
+
def validate_user(github_username):
|
|
100
|
+
# If restrict_to is configured, validate the user
|
|
101
|
+
if restrict_to:
|
|
102
|
+
# Parse allowed users (comma or space delimited)
|
|
103
|
+
allowed_users = [u.strip() for u in re.split(r"[,\s]+", restrict_to) if u.strip()]
|
|
104
|
+
|
|
105
|
+
# Check if user is in the allowed list
|
|
106
|
+
if not github_username or github_username not in allowed_users:
|
|
107
|
+
ctx.log(f"Access denied for user: {github_username}. Not in allowed list: {allowed_users}")
|
|
108
|
+
return web.Response(
|
|
109
|
+
text=f"Access denied. User '{github_username}' is not authorized to access this application.",
|
|
110
|
+
status=403,
|
|
111
|
+
)
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
async def github_callback_handler(request):
|
|
115
|
+
"""Handle GitHub OAuth callback"""
|
|
116
|
+
code = request.query.get("code")
|
|
117
|
+
state = request.query.get("state")
|
|
118
|
+
|
|
119
|
+
# Handle malformed URLs where query params are appended with & instead of ?
|
|
120
|
+
if not code and "tail" in request.match_info:
|
|
121
|
+
tail = request.match_info["tail"]
|
|
122
|
+
if tail.startswith("&"):
|
|
123
|
+
params = parse_qs(tail[1:])
|
|
124
|
+
code = params.get("code", [None])[0]
|
|
125
|
+
state = params.get("state", [None])[0]
|
|
126
|
+
|
|
127
|
+
if not code or not state:
|
|
128
|
+
return web.Response(text="Missing code or state parameter", status=400)
|
|
129
|
+
|
|
130
|
+
# Verify state token (CSRF protection)
|
|
131
|
+
if state not in ctx.oauth_states:
|
|
132
|
+
return web.Response(text="Invalid state parameter", status=400)
|
|
133
|
+
|
|
134
|
+
ctx.oauth_states.pop(state)
|
|
135
|
+
|
|
136
|
+
# Exchange code for access token
|
|
137
|
+
async with aiohttp.ClientSession() as session:
|
|
138
|
+
token_url = "https://github.com/login/oauth/access_token"
|
|
139
|
+
token_data = {
|
|
140
|
+
"client_id": client_id,
|
|
141
|
+
"client_secret": client_secret,
|
|
142
|
+
"code": code,
|
|
143
|
+
"redirect_uri": redirect_uri,
|
|
144
|
+
}
|
|
145
|
+
headers = {"Accept": "application/json"}
|
|
146
|
+
|
|
147
|
+
async with session.post(token_url, data=token_data, headers=headers) as resp:
|
|
148
|
+
token_response = await resp.json()
|
|
149
|
+
access_token = token_response.get("access_token")
|
|
150
|
+
|
|
151
|
+
if not access_token:
|
|
152
|
+
error = token_response.get("error_description", "Failed to get access token")
|
|
153
|
+
return web.json_response(ctx.create_error_response(f"OAuth error: {error}"), status=400)
|
|
154
|
+
|
|
155
|
+
# Fetch user info
|
|
156
|
+
user_url = "https://api.github.com/user"
|
|
157
|
+
headers = {"Authorization": f"Bearer {access_token}", "Accept": "application/json"}
|
|
158
|
+
|
|
159
|
+
async with session.get(user_url, headers=headers) as resp:
|
|
160
|
+
user_data = await resp.json()
|
|
161
|
+
|
|
162
|
+
# Validate user
|
|
163
|
+
error_response = validate_user(user_data.get("login", ""))
|
|
164
|
+
if error_response:
|
|
165
|
+
return error_response
|
|
166
|
+
|
|
167
|
+
# Create session
|
|
168
|
+
session_token = secrets.token_urlsafe(32)
|
|
169
|
+
ctx.sessions[session_token] = {
|
|
170
|
+
"userId": str(user_data.get("id", "")),
|
|
171
|
+
"userName": user_data.get("login", ""),
|
|
172
|
+
"displayName": user_data.get("name", ""),
|
|
173
|
+
"profileUrl": user_data.get("avatar_url", ""),
|
|
174
|
+
"email": user_data.get("email", ""),
|
|
175
|
+
"created": time.time(),
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# Redirect to UI with session token
|
|
179
|
+
response = web.HTTPFound(f"/?session={session_token}")
|
|
180
|
+
response.set_cookie("llms-token", session_token, httponly=True, path="/", max_age=86400)
|
|
181
|
+
return response
|
|
182
|
+
|
|
183
|
+
async def session_handler(request):
|
|
184
|
+
"""Validate and return session info"""
|
|
185
|
+
session_token = auth_provider.get_session_token(request)
|
|
186
|
+
|
|
187
|
+
if not session_token or session_token not in ctx.sessions:
|
|
188
|
+
return web.json_response(ctx.create_error_response("Invalid or expired session"), status=401)
|
|
189
|
+
|
|
190
|
+
session_data = ctx.sessions[session_token]
|
|
191
|
+
|
|
192
|
+
# Clean up old sessions (older than 24 hours)
|
|
193
|
+
current_time = time.time()
|
|
194
|
+
expired_sessions = [token for token, data in ctx.sessions.items() if current_time - data["created"] > 86400]
|
|
195
|
+
for token in expired_sessions:
|
|
196
|
+
del ctx.sessions[token]
|
|
197
|
+
|
|
198
|
+
return web.json_response({**session_data, "sessionToken": session_token})
|
|
199
|
+
|
|
200
|
+
async def logout_handler(request):
|
|
201
|
+
"""End OAuth session"""
|
|
202
|
+
session_token = auth_provider.get_session_token(request)
|
|
203
|
+
|
|
204
|
+
if session_token and session_token in g_app.sessions:
|
|
205
|
+
del g_app.sessions[session_token]
|
|
206
|
+
|
|
207
|
+
response = web.json_response({"success": True})
|
|
208
|
+
response.del_cookie("llms-token")
|
|
209
|
+
return response
|
|
210
|
+
|
|
211
|
+
async def auth_handler(request):
|
|
212
|
+
"""Check authentication status and return user info"""
|
|
213
|
+
# Check for OAuth session token
|
|
214
|
+
session_token = auth_provider.get_session_token(request)
|
|
215
|
+
|
|
216
|
+
if session_token and session_token in g_app.sessions:
|
|
217
|
+
session_data = g_app.sessions[session_token]
|
|
218
|
+
return web.json_response(
|
|
219
|
+
{
|
|
220
|
+
"userId": session_data.get("userId", ""),
|
|
221
|
+
"userName": session_data.get("userName", ""),
|
|
222
|
+
"displayName": session_data.get("displayName", ""),
|
|
223
|
+
"profileUrl": session_data.get("profileUrl", ""),
|
|
224
|
+
"authProvider": "github",
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Check for API key in Authorization header
|
|
229
|
+
# auth_header = request.headers.get('Authorization', '')
|
|
230
|
+
# if auth_header.startswith('Bearer '):
|
|
231
|
+
# # For API key auth, return a basic response
|
|
232
|
+
# # You can customize this based on your API key validation logic
|
|
233
|
+
# api_key = auth_header[7:]
|
|
234
|
+
# if api_key: # Add your API key validation logic here
|
|
235
|
+
# return web.json_response({
|
|
236
|
+
# "userId": "1",
|
|
237
|
+
# "userName": "apiuser",
|
|
238
|
+
# "displayName": "API User",
|
|
239
|
+
# "profileUrl": "",
|
|
240
|
+
# "authProvider": "apikey"
|
|
241
|
+
# })
|
|
242
|
+
|
|
243
|
+
# Not authenticated - return error in expected format
|
|
244
|
+
return web.json_response(g_app.error_auth_required, status=401)
|
|
245
|
+
|
|
246
|
+
ctx.add_get("/auth", auth_handler)
|
|
247
|
+
ctx.add_get("/auth/github", github_auth_handler)
|
|
248
|
+
ctx.add_get("/auth/github/callback", github_callback_handler)
|
|
249
|
+
ctx.add_get("/auth/github/callback{tail:.*}", github_callback_handler)
|
|
250
|
+
ctx.add_get("/auth/session", session_handler)
|
|
251
|
+
ctx.add_post("/auth/logout", logout_handler)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
__install__ = install
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ref } from "vue"
|
|
2
|
+
|
|
3
|
+
const SignIn = {
|
|
4
|
+
template: `
|
|
5
|
+
<div class="min-h-full -mt-36 flex flex-col justify-center sm:px-6 lg:px-8">
|
|
6
|
+
<div class="sm:mx-auto sm:w-full sm:max-w-md text-center">
|
|
7
|
+
<Welcome />
|
|
8
|
+
</div>
|
|
9
|
+
<div class="sm:mx-auto sm:w-full sm:max-w-md">
|
|
10
|
+
<div v-if="errorMessage" class="mb-3 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 text-red-800 dark:text-red-200 rounded-lg px-4 py-3">
|
|
11
|
+
<div class="flex items-start space-x-2">
|
|
12
|
+
<div class="flex-1">
|
|
13
|
+
<div class="text-base font-medium">{{ errorMessage }}</div>
|
|
14
|
+
</div>
|
|
15
|
+
<button type="button"
|
|
16
|
+
@click="errorMessage = null"
|
|
17
|
+
class="text-red-400 dark:text-red-300 hover:text-red-600 dark:hover:text-red-100 flex-shrink-0"
|
|
18
|
+
>
|
|
19
|
+
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
20
|
+
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
|
21
|
+
</svg>
|
|
22
|
+
</button>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="py-8 px-4 sm:px-10">
|
|
26
|
+
<div class="space-y-4">
|
|
27
|
+
<button
|
|
28
|
+
type="button"
|
|
29
|
+
@click="signInWithGitHub"
|
|
30
|
+
class="w-full inline-flex items-center justify-center px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-base font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 transition-colors"
|
|
31
|
+
>
|
|
32
|
+
<svg class="w-6 h-6 mr-3" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
33
|
+
<path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd" />
|
|
34
|
+
</svg>
|
|
35
|
+
Sign in with GitHub
|
|
36
|
+
</button>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
`,
|
|
42
|
+
emits: ['done'],
|
|
43
|
+
setup(props, { emit }) {
|
|
44
|
+
const errorMessage = ref(null)
|
|
45
|
+
|
|
46
|
+
function signInWithGitHub() {
|
|
47
|
+
// Redirect to GitHub OAuth endpoint
|
|
48
|
+
window.location.href = '/auth/github'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
signInWithGitHub,
|
|
53
|
+
errorMessage,
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
export default {
|
|
60
|
+
install(ctx) {
|
|
61
|
+
// Override SignIn component
|
|
62
|
+
ctx.components({
|
|
63
|
+
SignIn,
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -33,14 +33,21 @@ Access the skills panel by clicking the **Skills** icon in the top toolbar. The
|
|
|
33
33
|
|
|
34
34
|
### Skill Groups
|
|
35
35
|
|
|
36
|
-
Skills are organized into groups based on their source:
|
|
36
|
+
Skills are organized into groups based on their source location. Skills are discovered from these directories in order:
|
|
37
37
|
|
|
38
|
-
| Group | Description | Editable |
|
|
39
|
-
|
|
40
|
-
|
|
|
41
|
-
|
|
|
38
|
+
| Group | Location | Description | Editable |
|
|
39
|
+
|-------|----------|-------------|----------|
|
|
40
|
+
| Project (Agent) | `.agent/skills/` | Skills local to the current project | ✓ Yes |
|
|
41
|
+
| Project (Claude) | `.claude/skills/` | Claude-format skills in the current project | ✓ Yes |
|
|
42
|
+
| User (Agent) | `~/.llms/.agents/skills/` | Your personal skills collection | ✓ Yes |
|
|
43
|
+
| User (Claude) | `~/.claude/skills/` | Claude-format skills in your home directory | ✓ Yes |
|
|
44
|
+
| Built-in | Extension directory | Skills bundled with the extension | ✗ No |
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
**Project-level skills** (`.agent/` and `.claude/`) are specific to the workspace you're working in. They're ideal for project-specific workflows, coding standards, or team conventions.
|
|
47
|
+
|
|
48
|
+
**User-level skills** (`~/.llms/.agents/` and `~/.claude/`) are available across all projects. Use these for personal workflows and preferences.
|
|
49
|
+
|
|
50
|
+
Both `.agent` and `.claude` directory formats are supported for compatibility with different tooling conventions.
|
|
44
51
|
|
|
45
52
|
### Selecting Skills for a Conversation
|
|
46
53
|
|
|
@@ -21,6 +21,9 @@ g_home_skills = None
|
|
|
21
21
|
# }
|
|
22
22
|
g_available_skills = []
|
|
23
23
|
|
|
24
|
+
LLMS_HOME_SKILLS = "~/.llms/.agent/skills"
|
|
25
|
+
LLMS_LOCAL_SKILLS = ".agent/skills"
|
|
26
|
+
|
|
24
27
|
|
|
25
28
|
def is_safe_path(base_path: str, requested_path: str) -> bool:
|
|
26
29
|
"""Check if the requested path is safely within the base path."""
|
|
@@ -132,10 +135,12 @@ def install(ctx):
|
|
|
132
135
|
if os.path.exists(os.path.join(".claude", "skills")):
|
|
133
136
|
skill_roots[".claude/skills"] = os.path.join(".claude", "skills")
|
|
134
137
|
|
|
135
|
-
skill_roots[
|
|
138
|
+
skill_roots[LLMS_HOME_SKILLS] = home_skills
|
|
136
139
|
|
|
137
|
-
|
|
138
|
-
|
|
140
|
+
local_skills = os.path.join(".agent", "skills")
|
|
141
|
+
if os.path.exists(local_skills):
|
|
142
|
+
local_skills = str(Path(local_skills).resolve())
|
|
143
|
+
skill_roots[LLMS_LOCAL_SKILLS] = local_skills
|
|
139
144
|
|
|
140
145
|
g_skills = {}
|
|
141
146
|
for group, root in skill_roots.items():
|
|
@@ -237,7 +242,7 @@ def install(ctx):
|
|
|
237
242
|
skill_props = props.to_dict()
|
|
238
243
|
skill_props.update(
|
|
239
244
|
{
|
|
240
|
-
"group":
|
|
245
|
+
"group": LLMS_HOME_SKILLS,
|
|
241
246
|
"location": str(skill_dir),
|
|
242
247
|
"files": files,
|
|
243
248
|
}
|
|
@@ -303,9 +308,9 @@ def install(ctx):
|
|
|
303
308
|
|
|
304
309
|
location = skill_info.get("location")
|
|
305
310
|
|
|
306
|
-
# Only allow modifications to skills in home directory
|
|
307
|
-
if not is_safe_path(home_skills, location):
|
|
308
|
-
raise Exception("Cannot modify skills outside of
|
|
311
|
+
# Only allow modifications to skills in home or local .agent directory
|
|
312
|
+
if not is_safe_path(home_skills, location) and not (local_skills and is_safe_path(local_skills, location)):
|
|
313
|
+
raise Exception("Cannot modify skills outside of allowed directories")
|
|
309
314
|
|
|
310
315
|
full_path = os.path.join(location, file_path)
|
|
311
316
|
|
|
@@ -319,7 +324,7 @@ def install(ctx):
|
|
|
319
324
|
f.write(content)
|
|
320
325
|
|
|
321
326
|
# Reload skill metadata
|
|
322
|
-
group = skill_info.get("group",
|
|
327
|
+
group = skill_info.get("group", LLMS_HOME_SKILLS)
|
|
323
328
|
updated_skill = reload_skill(name, location, group)
|
|
324
329
|
|
|
325
330
|
return aiohttp.web.json_response({"path": file_path, "skill": updated_skill})
|
|
@@ -342,9 +347,9 @@ def install(ctx):
|
|
|
342
347
|
|
|
343
348
|
location = skill_info.get("location")
|
|
344
349
|
|
|
345
|
-
# Only allow modifications to skills in home directory
|
|
346
|
-
if not is_safe_path(home_skills, location):
|
|
347
|
-
raise Exception("Cannot modify skills outside of
|
|
350
|
+
# Only allow modifications to skills in home or local .agent directory
|
|
351
|
+
if not is_safe_path(home_skills, location) and not (local_skills and is_safe_path(local_skills, location)):
|
|
352
|
+
raise Exception("Cannot modify skills outside of allowed directories")
|
|
348
353
|
|
|
349
354
|
full_path = os.path.join(location, file_path)
|
|
350
355
|
|
|
@@ -371,7 +376,7 @@ def install(ctx):
|
|
|
371
376
|
break
|
|
372
377
|
|
|
373
378
|
# Reload skill metadata
|
|
374
|
-
group = skill_info.get("group",
|
|
379
|
+
group = skill_info.get("group", LLMS_HOME_SKILLS)
|
|
375
380
|
updated_skill = reload_skill(name, location, group)
|
|
376
381
|
|
|
377
382
|
return aiohttp.web.json_response({"path": file_path, "skill": updated_skill})
|
|
@@ -433,7 +438,7 @@ def install(ctx):
|
|
|
433
438
|
skill_props = props.to_dict()
|
|
434
439
|
skill_props.update(
|
|
435
440
|
{
|
|
436
|
-
"group":
|
|
441
|
+
"group": LLMS_HOME_SKILLS,
|
|
437
442
|
"location": str(skill_dir_path),
|
|
438
443
|
"files": files,
|
|
439
444
|
}
|
|
@@ -467,9 +472,9 @@ def install(ctx):
|
|
|
467
472
|
else:
|
|
468
473
|
raise Exception(f"Skill '{name}' not found")
|
|
469
474
|
|
|
470
|
-
# Only allow deletion of skills in home directory
|
|
471
|
-
if not is_safe_path(home_skills, location):
|
|
472
|
-
raise Exception("Cannot delete skills outside of
|
|
475
|
+
# Only allow deletion of skills in home or local .agent directory
|
|
476
|
+
if not is_safe_path(home_skills, location) and not (local_skills and is_safe_path(local_skills, location)):
|
|
477
|
+
raise Exception("Cannot delete skills outside of allowed directories")
|
|
473
478
|
|
|
474
479
|
try:
|
|
475
480
|
if os.path.exists(location):
|