llms-py 3.0.22__py3-none-any.whl → 3.0.24__py3-none-any.whl
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/extensions/skills/README.md +13 -6
- llms/extensions/skills/__init__.py +21 -16
- llms/extensions/skills/ui/index.mjs +15 -8
- llms/llms.json +16 -4
- llms/main.py +17 -5
- llms/providers.json +1 -1
- llms/ui/ai.mjs +1 -1
- llms/ui/app.css +15 -3
- {llms_py-3.0.22.dist-info → llms_py-3.0.24.dist-info}/METADATA +1 -1
- {llms_py-3.0.22.dist-info → llms_py-3.0.24.dist-info}/RECORD +14 -14
- {llms_py-3.0.22.dist-info → llms_py-3.0.24.dist-info}/WHEEL +0 -0
- {llms_py-3.0.22.dist-info → llms_py-3.0.24.dist-info}/entry_points.txt +0 -0
- {llms_py-3.0.22.dist-info → llms_py-3.0.24.dist-info}/licenses/LICENSE +0 -0
- {llms_py-3.0.22.dist-info → llms_py-3.0.24.dist-info}/top_level.txt +0 -0
llms/extensions/skills/README.md
CHANGED
|
@@ -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):
|
|
@@ -3,6 +3,9 @@ import { leftPart } from "@servicestack/client"
|
|
|
3
3
|
|
|
4
4
|
let ext
|
|
5
5
|
|
|
6
|
+
const LLMS_HOME_SKILLS = "~/.llms/.agent/skills"
|
|
7
|
+
const LLMS_LOCAL_SKILLS = ".agent/skills"
|
|
8
|
+
|
|
6
9
|
const SkillSelector = {
|
|
7
10
|
template: `
|
|
8
11
|
<div class="px-4 py-4 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 max-h-[80vh] overflow-y-auto">
|
|
@@ -119,10 +122,10 @@ const SkillSelector = {
|
|
|
119
122
|
skills
|
|
120
123
|
}))
|
|
121
124
|
|
|
122
|
-
// Sort groups: writable (~/.llms/.
|
|
125
|
+
// Sort groups: writable (~/.llms/.agent/skills,.agent/skills) first, then alphabetically
|
|
123
126
|
definedGroups.sort((a, b) => {
|
|
124
|
-
const aEditable = a.name ===
|
|
125
|
-
const bEditable = b.name ===
|
|
127
|
+
const aEditable = a.name === LLMS_HOME_SKILLS || a.name === LLMS_LOCAL_SKILLS
|
|
128
|
+
const bEditable = b.name === LLMS_HOME_SKILLS || b.name === LLMS_LOCAL_SKILLS
|
|
126
129
|
if (aEditable !== bEditable) return aEditable ? -1 : 1
|
|
127
130
|
return a.name.localeCompare(b.name)
|
|
128
131
|
})
|
|
@@ -158,6 +161,10 @@ const SkillSelector = {
|
|
|
158
161
|
onlySkills = onlySkills.filter(s => s !== name)
|
|
159
162
|
} else {
|
|
160
163
|
onlySkills = [...onlySkills, name]
|
|
164
|
+
// If has all skills set to 'All' (null)
|
|
165
|
+
if (onlySkills.length === availableSkills.value.length) {
|
|
166
|
+
onlySkills = null
|
|
167
|
+
}
|
|
161
168
|
}
|
|
162
169
|
}
|
|
163
170
|
|
|
@@ -395,8 +402,8 @@ const SkillPage = {
|
|
|
395
402
|
grouped[group].push(skill)
|
|
396
403
|
})
|
|
397
404
|
return Object.entries(grouped).sort((a, b) => {
|
|
398
|
-
const aEditable = a[0] ===
|
|
399
|
-
const bEditable = b[0] ===
|
|
405
|
+
const aEditable = a[0] === LLMS_HOME_SKILLS || a[0] === LLMS_LOCAL_SKILLS
|
|
406
|
+
const bEditable = b[0] === LLMS_HOME_SKILLS || b[0] === LLMS_LOCAL_SKILLS
|
|
400
407
|
if (aEditable !== bEditable) return aEditable ? -1 : 1
|
|
401
408
|
return a[0].localeCompare(b[0])
|
|
402
409
|
}).map(([name, skills]) => ({ name, skills: skills.sort((a, b) => a.name.localeCompare(b.name)) }))
|
|
@@ -419,8 +426,8 @@ const SkillPage = {
|
|
|
419
426
|
return tree.sort((a, b) => { if (a.isFile !== b.isFile) return a.isFile ? 1 : -1; return a.name.localeCompare(b.name) })
|
|
420
427
|
}
|
|
421
428
|
const hasUnsavedChanges = computed(() => isEditing.value && editContent.value !== fileContent.value)
|
|
422
|
-
function isGroupEditable(groupName) { return groupName ===
|
|
423
|
-
function isEditable(skill) { return skill?.group ===
|
|
429
|
+
function isGroupEditable(groupName) { return groupName === LLMS_HOME_SKILLS || groupName === LLMS_LOCAL_SKILLS }
|
|
430
|
+
function isEditable(skill) { return skill?.group === LLMS_HOME_SKILLS || skill?.group === LLMS_LOCAL_SKILLS }
|
|
424
431
|
function isSkillExpanded(name) { return !!expandedSkills.value[name] }
|
|
425
432
|
function toggleSkillExpand(skill) {
|
|
426
433
|
expandedSkills.value[skill.name] = !expandedSkills.value[skill.name]
|
|
@@ -579,7 +586,7 @@ const SkillStore = {
|
|
|
579
586
|
<div class="h-full flex flex-col">
|
|
580
587
|
<div class="px-4 py-3 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between flex-shrink-0">
|
|
581
588
|
<div>
|
|
582
|
-
<h1 class="text-xl font-bold text-gray-900 dark:text-gray-100">
|
|
589
|
+
<h1 class="text-xl font-bold text-gray-900 dark:text-gray-100">Discover Skills</h1>
|
|
583
590
|
<p class="text-sm text-gray-500 dark:text-gray-400">{{ total.toLocaleString() }} skills available</p>
|
|
584
591
|
</div>
|
|
585
592
|
<div class="flex items-center gap-2">
|
llms/llms.json
CHANGED
|
@@ -235,17 +235,29 @@
|
|
|
235
235
|
"enabled": true,
|
|
236
236
|
"temperature": 1.0
|
|
237
237
|
},
|
|
238
|
+
"lmstudio": {
|
|
239
|
+
"enabled": false,
|
|
240
|
+
"npm": "lmstudio",
|
|
241
|
+
"api": "http://127.0.0.1:1234/v1",
|
|
242
|
+
"models": {}
|
|
243
|
+
},
|
|
238
244
|
"ollama": {
|
|
239
245
|
"enabled": false,
|
|
240
246
|
"id": "ollama",
|
|
241
247
|
"npm": "ollama",
|
|
242
248
|
"api": "http://localhost:11434"
|
|
243
249
|
},
|
|
244
|
-
"
|
|
250
|
+
"ollama-cloud": {
|
|
251
|
+
"enabled": true,
|
|
252
|
+
"env": [
|
|
253
|
+
"OLLAMA_API_KEY"
|
|
254
|
+
]
|
|
255
|
+
},
|
|
256
|
+
"openai-local": {
|
|
245
257
|
"enabled": false,
|
|
246
|
-
"npm": "
|
|
247
|
-
"api": "http://
|
|
248
|
-
"
|
|
258
|
+
"npm": "openai-local",
|
|
259
|
+
"api": "http://localhost:8000/v1",
|
|
260
|
+
"api_key": "$OPENAI_LOCAL_API_KEY"
|
|
249
261
|
},
|
|
250
262
|
"google": {
|
|
251
263
|
"enabled": true,
|
llms/main.py
CHANGED
|
@@ -57,7 +57,7 @@ try:
|
|
|
57
57
|
except ImportError:
|
|
58
58
|
HAS_PIL = False
|
|
59
59
|
|
|
60
|
-
VERSION = "3.0.
|
|
60
|
+
VERSION = "3.0.24"
|
|
61
61
|
_ROOT = None
|
|
62
62
|
DEBUG = os.getenv("DEBUG") == "1"
|
|
63
63
|
MOCK = os.getenv("MOCK") == "1"
|
|
@@ -871,8 +871,7 @@ def save_image_to_cache(base64_data, filename, image_info, ignore_info=False):
|
|
|
871
871
|
return url, info
|
|
872
872
|
|
|
873
873
|
|
|
874
|
-
|
|
875
|
-
text = await response.text()
|
|
874
|
+
def http_error_to_message(response, text):
|
|
876
875
|
if response.status >= 400:
|
|
877
876
|
message = "HTTP " + str(response.status) + " " + response.reason
|
|
878
877
|
_dbg(f"HTTP {response.status} {response.reason}\n{dict(response.headers)}\n{text}")
|
|
@@ -885,6 +884,13 @@ async def response_json(response):
|
|
|
885
884
|
except Exception:
|
|
886
885
|
if text:
|
|
887
886
|
message += ": " + text[:100]
|
|
887
|
+
return message
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
async def response_json(response):
|
|
891
|
+
text = await response.text()
|
|
892
|
+
if response.status >= 400:
|
|
893
|
+
message = http_error_to_message(response, text)
|
|
888
894
|
raise Exception(message)
|
|
889
895
|
response.raise_for_status()
|
|
890
896
|
body = json.loads(text)
|
|
@@ -1394,6 +1400,7 @@ class OllamaProvider(OpenAiCompatible):
|
|
|
1394
1400
|
"id": k,
|
|
1395
1401
|
"name": v.replace(":", " "),
|
|
1396
1402
|
"modalities": {"input": ["text"], "output": ["text"]},
|
|
1403
|
+
"tool_call": True,
|
|
1397
1404
|
"cost": {
|
|
1398
1405
|
"input": 0,
|
|
1399
1406
|
"output": 0,
|
|
@@ -1431,6 +1438,10 @@ class LMStudioProvider(OllamaProvider):
|
|
|
1431
1438
|
return ret
|
|
1432
1439
|
|
|
1433
1440
|
|
|
1441
|
+
class OpenAiLocalProvider(LMStudioProvider):
|
|
1442
|
+
sdk = "openai-local"
|
|
1443
|
+
|
|
1444
|
+
|
|
1434
1445
|
def get_provider_model(model_name):
|
|
1435
1446
|
for provider in g_handlers.values():
|
|
1436
1447
|
provider_model = provider.provider_model(model_name)
|
|
@@ -2229,7 +2240,7 @@ async def get_text(url):
|
|
|
2229
2240
|
async with session.get(url) as resp:
|
|
2230
2241
|
text = await resp.text()
|
|
2231
2242
|
if resp.status >= 400:
|
|
2232
|
-
raise
|
|
2243
|
+
raise Exception(http_error_to_message(resp, text))
|
|
2233
2244
|
return text
|
|
2234
2245
|
|
|
2235
2246
|
|
|
@@ -2838,6 +2849,7 @@ class AppExtensions:
|
|
|
2838
2849
|
CodestralProvider,
|
|
2839
2850
|
OllamaProvider,
|
|
2840
2851
|
LMStudioProvider,
|
|
2852
|
+
OpenAiLocalProvider,
|
|
2841
2853
|
]
|
|
2842
2854
|
self.aspect_ratios = {
|
|
2843
2855
|
"1:1": "1024×1024",
|
|
@@ -2953,7 +2965,7 @@ class AppExtensions:
|
|
|
2953
2965
|
for filter_func in self.chat_error_filters:
|
|
2954
2966
|
try:
|
|
2955
2967
|
task = filter_func(e, context)
|
|
2956
|
-
if
|
|
2968
|
+
if inspect.iscoroutine(task):
|
|
2957
2969
|
await task
|
|
2958
2970
|
except Exception as e:
|
|
2959
2971
|
_err("chat error filter failed", e)
|