bugfix
This commit is contained in:
parent
6dda573aed
commit
6cc1fdeb4e
@ -6,7 +6,17 @@ from pydantic import BaseModel, Field, ValidationError
|
|||||||
from typing import Literal
|
from typing import Literal
|
||||||
from appPublic.worker import awaitify
|
from appPublic.worker import awaitify
|
||||||
from appPublic.streamhttpclient import StreamHttpClient, liner
|
from appPublic.streamhttpclient import StreamHttpClient, liner
|
||||||
|
from appPublic.dictObject import DictObject
|
||||||
|
from appPublic.log import debug, exception, info
|
||||||
|
from ahserver.serverenv import ServerEnv
|
||||||
from .skillkit_wrapper import SkillkitWrapper
|
from .skillkit_wrapper import SkillkitWrapper
|
||||||
|
from .params import ParameterResolver, ResolvedParams, MissingParams
|
||||||
|
from .tmpls import (
|
||||||
|
three_candidates_tmpl,
|
||||||
|
resume_tmpl,
|
||||||
|
ask_user_reply_tmpl,
|
||||||
|
choose_candidates_tmpl
|
||||||
|
)
|
||||||
|
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
# Skill Decision / PlanState
|
# Skill Decision / PlanState
|
||||||
@ -14,6 +24,7 @@ from .skillkit_wrapper import SkillkitWrapper
|
|||||||
@dataclass
|
@dataclass
|
||||||
class SkillDecision:
|
class SkillDecision:
|
||||||
skill: str
|
skill: str
|
||||||
|
script: str
|
||||||
params: dict
|
params: dict
|
||||||
reason: Optional[str] = None
|
reason: Optional[str] = None
|
||||||
|
|
||||||
@ -29,8 +40,8 @@ class PlanState:
|
|||||||
# 自定义异常
|
# 自定义异常
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
class MissingParams(Exception):
|
class MissingParams(Exception):
|
||||||
def __init__(self, skill: str, fields: List[str]):
|
def __init__(self, decision: SkillDecision, fields: List[str]):
|
||||||
self.skill = skill
|
self.decision = decision
|
||||||
self.fields = fields
|
self.fields = fields
|
||||||
|
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
@ -69,8 +80,7 @@ class DummyLLM(LLM):
|
|||||||
continue
|
continue
|
||||||
if d.get('content'):
|
if d.get('content'):
|
||||||
doc = f'{doc}{d["content"]}'
|
doc = f'{doc}{d["content"]}'
|
||||||
else:
|
debug(f'{doc=}')
|
||||||
print(f'{d} error')
|
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
@ -86,9 +96,19 @@ class Agent:
|
|||||||
def load_skills(self):
|
def load_skills(self):
|
||||||
if self.loaded:
|
if self.loaded:
|
||||||
return
|
return
|
||||||
self.skills = self.skillkit.list_skills()
|
skills = self.skillkit.list_skills()
|
||||||
for s in self.skills:
|
self.skills = []
|
||||||
self.skillkit.load_skill(s.name)
|
for s in skills:
|
||||||
|
sk = self.skillkit.load_skill(s.name)
|
||||||
|
self.skills.append(sk)
|
||||||
|
|
||||||
|
async def render_txt(self, tmpl_name, lang, data):
|
||||||
|
env = ServerEnv()
|
||||||
|
engine = env.tmpl_engine
|
||||||
|
tmpl = tmpl_name.get(lang)
|
||||||
|
if not tmpl:
|
||||||
|
return None
|
||||||
|
return await engine.renders(tmpl, data)
|
||||||
|
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
# plan: 多 skill 候选 + 参数抽取
|
# plan: 多 skill 候选 + 参数抽取
|
||||||
@ -100,7 +120,7 @@ class Agent:
|
|||||||
try:
|
try:
|
||||||
validated_params = self._validate_params(decision)
|
validated_params = self._validate_params(decision)
|
||||||
except MissingParams as e:
|
except MissingParams as e:
|
||||||
question = await self._ask_user_for_params(user_text, decision.skill, e.fields)
|
question = await self._ask_user_for_params(user_text, decision.skill, decision.script, e.fields)
|
||||||
state = PlanState(
|
state = PlanState(
|
||||||
user_intent=user_text,
|
user_intent=user_text,
|
||||||
skill=decision.skill,
|
skill=decision.skill,
|
||||||
@ -124,6 +144,7 @@ class Agent:
|
|||||||
def get_scripts(self, skillname):
|
def get_scripts(self, skillname):
|
||||||
return self.skillkit.get_skill_scripts(skillname)
|
return self.skillkit.get_skill_scripts(skillname)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
# resume: 补 missing 参数
|
# resume: 补 missing 参数
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
@ -132,31 +153,13 @@ class Agent:
|
|||||||
schema_fields = self.skillkit.get_script_params(state.skill, state.script)
|
schema_fields = self.skillkit.get_script_params(state.skill, state.script)
|
||||||
if schema_fields is None:
|
if schema_fields is None:
|
||||||
schema_fields = []
|
schema_fields = []
|
||||||
prompt = f"""
|
data = {
|
||||||
You are an agent helping a user fill parameters for a skill.
|
'state': state,
|
||||||
|
'kit': self.skillkit,
|
||||||
Skill name: {state.skill}
|
'json': json,
|
||||||
Script name: {state.script}
|
'user_reply': user_reply
|
||||||
Skill required parameters: {schema_fields}
|
}
|
||||||
|
prompt = await self.render_txt(resume_tmpl, 'zh', data)
|
||||||
User original intent:
|
|
||||||
\"\"\"{state.user_intent}\"\"\"
|
|
||||||
|
|
||||||
Known parameters:
|
|
||||||
{state.params}
|
|
||||||
|
|
||||||
Missing parameters:
|
|
||||||
{state.missing}
|
|
||||||
|
|
||||||
User reply:
|
|
||||||
\"\"\"{user_reply}\"\"\"
|
|
||||||
|
|
||||||
Task:
|
|
||||||
- Extract values ONLY for missing parameters.
|
|
||||||
- Do NOT modify existing known parameters.
|
|
||||||
- All output must match the skill parameter schema.
|
|
||||||
- Output JSON only with the missing parameters.
|
|
||||||
"""
|
|
||||||
raw = await self.llm.complete(prompt)
|
raw = await self.llm.complete(prompt)
|
||||||
new_params = json.loads(raw)
|
new_params = json.loads(raw)
|
||||||
|
|
||||||
@ -164,7 +167,7 @@ Task:
|
|||||||
|
|
||||||
# 校验 schema
|
# 校验 schema
|
||||||
try:
|
try:
|
||||||
validated = self._validate_params(SkillDecision(skill=state.skill, params=state.params))
|
validated = self._validate_params(SkillDecision(skill=state.skill, script=state.script, params=state.params))
|
||||||
except MissingParams as e:
|
except MissingParams as e:
|
||||||
state.missing = e.fields
|
state.missing = e.fields
|
||||||
question = await self._ask_user_for_params(state.user_intent, state.skill, e.fields)
|
question = await self._ask_user_for_params(state.user_intent, state.skill, e.fields)
|
||||||
@ -187,75 +190,55 @@ Task:
|
|||||||
return "Scripts: " + '::'.join(d)
|
return "Scripts: " + '::'.join(d)
|
||||||
|
|
||||||
async def _candidate_skills(self, user_text: str):
|
async def _candidate_skills(self, user_text: str):
|
||||||
skill_list = "\n".join(f"- skillname:{s.name}({s.description}): {self.scripts_info(s.name)}" for s in self.skills)
|
data = {
|
||||||
prompt = f"""
|
'kit': self.skillkit,
|
||||||
User request:
|
'prompt': user_text,
|
||||||
\"\"\"{user_text}\"\"\"
|
'json': json,
|
||||||
|
'skills': self.skills
|
||||||
Available skills:
|
}
|
||||||
{skill_list}
|
prompt = await self.render_txt(three_candidates_tmpl, 'zh', data)
|
||||||
|
debug(f'{prompt=}')
|
||||||
Task:
|
|
||||||
Select up to 3 most relevant skill's scripts.
|
|
||||||
|
|
||||||
Output JSON list only.
|
|
||||||
"""
|
|
||||||
raw = await self.llm.complete(prompt)
|
raw = await self.llm.complete(prompt)
|
||||||
|
debug(f'{raw=}')
|
||||||
return json.loads(raw)
|
return json.loads(raw)
|
||||||
|
|
||||||
async def _plan_with_candidates(self, user_text: str, candidates: list[str]):
|
async def _plan_with_candidates(self, user_text: str, candidates: list):
|
||||||
specs = [s for s in self.skills if s.name in candidates]
|
data = {
|
||||||
spec_desc = "\n".join(
|
'prompt': user_text,
|
||||||
f"- {s.name}: inputs={list(s.schema.model_fields.keys())}" for s in specs if s.schema
|
'candidates': candidates,
|
||||||
)
|
'json': json,
|
||||||
prompt = f"""
|
'kit': self.skillkit
|
||||||
User request:
|
}
|
||||||
\"\"\"{user_text}\"\"\"
|
prompt = await self.render_txt(choose_candidates_tmpl, 'zh', data)
|
||||||
|
|
||||||
Candidate skills:
|
|
||||||
{spec_desc}
|
|
||||||
|
|
||||||
Task:
|
|
||||||
1. Choose the best skill.
|
|
||||||
2. Extract parameters strictly matching schema.
|
|
||||||
|
|
||||||
Rules:
|
|
||||||
- If a required parameter is missing, set it to null.
|
|
||||||
- Output JSON only.
|
|
||||||
|
|
||||||
Output:
|
|
||||||
{{
|
|
||||||
"skill": "...",
|
|
||||||
"script": "...",
|
|
||||||
"params": {{ ... }},
|
|
||||||
"reason": "..."
|
|
||||||
}}
|
|
||||||
"""
|
|
||||||
raw = await self.llm.complete(prompt)
|
raw = await self.llm.complete(prompt)
|
||||||
return SkillDecision(**json.loads(raw))
|
return SkillDecision(**json.loads(raw))
|
||||||
|
|
||||||
def _validate_params(self, decision: SkillDecision):
|
def _validate_params(self, decision: SkillDecision):
|
||||||
spec = next(s for s in self.skills if s.name == decision.skill)
|
spec = next(s for s in self.skills if s.metadata.name == decision.skill)
|
||||||
if not spec.schema:
|
schema = self.skillkit.get_script_params(decision.skill, decision.script)
|
||||||
|
if not schema:
|
||||||
return decision.params
|
return decision.params
|
||||||
try:
|
try:
|
||||||
return spec.schema(**decision.params).dict()
|
resolver = ParameterResolver(schema)
|
||||||
|
result = resolver.resolve(decision.params)
|
||||||
|
if isinstance(result, ResolvedParams):
|
||||||
|
return result.params
|
||||||
|
raise MissingParams(decision, result.missing)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
missing = [err["loc"][0] for err in e.errors() if err["type"] == "missing"]
|
missing = [err["loc"][0] for err in e.errors() if err["type"] == "missing"]
|
||||||
if missing:
|
if missing:
|
||||||
raise MissingParams(decision.skill, missing)
|
raise MissingParams(decision, missing)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
async def _ask_user_for_params(self, user_text: str, skill: str, fields: List[str]):
|
async def _ask_user_for_params(self, user_text: str, skill: str, script: str, fields: List[str]):
|
||||||
prompt = f"""
|
data = {
|
||||||
User request:
|
'user_text': user_text,
|
||||||
\"\"\"{user_text}\"\"\"
|
'skill': skill,
|
||||||
|
'script': script,
|
||||||
The script "{script} in skill "{skill}" requires the following missing parameters:
|
'json': json,
|
||||||
{fields}
|
'fields': fields
|
||||||
|
}
|
||||||
Ask the user a concise clarification question.
|
prompt = await self.render_txt(ask_user_reply_tmpl, 'zh', data)
|
||||||
"""
|
|
||||||
return await self.llm.complete(prompt)
|
return await self.llm.complete(prompt)
|
||||||
|
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
|
|||||||
90
skillagent/params.py
Normal file
90
skillagent/params.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class MissingParam:
|
||||||
|
name: str
|
||||||
|
type: Optional[str]
|
||||||
|
description: Optional[str]
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ResolvedParams:
|
||||||
|
params: Dict[str, Any]
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class MissingParams:
|
||||||
|
missing: List[MissingParam]
|
||||||
|
|
||||||
|
class ParameterResolver:
|
||||||
|
TYPE_MAP = {
|
||||||
|
"string": str,
|
||||||
|
"integer": int,
|
||||||
|
"number": (int, float),
|
||||||
|
"boolean": bool,
|
||||||
|
"array": list,
|
||||||
|
"object": dict,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, schema: Dict[str, dict]):
|
||||||
|
self.schema = schema
|
||||||
|
|
||||||
|
def resolve(self, params: Dict[str, Any]):
|
||||||
|
validated = {}
|
||||||
|
missing = []
|
||||||
|
|
||||||
|
for name, spec in self.schema.items():
|
||||||
|
required = spec.get("required", False)
|
||||||
|
default = spec.get("default")
|
||||||
|
expected_type = spec.get("type")
|
||||||
|
enum = spec.get("enum")
|
||||||
|
description = spec.get("description")
|
||||||
|
|
||||||
|
if name in params:
|
||||||
|
value = params[name]
|
||||||
|
else:
|
||||||
|
if required:
|
||||||
|
missing.append(
|
||||||
|
MissingParam(
|
||||||
|
name=name,
|
||||||
|
type=expected_type,
|
||||||
|
description=description,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
value = default
|
||||||
|
|
||||||
|
if value is None:
|
||||||
|
validated[name] = value
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 类型校验
|
||||||
|
if expected_type:
|
||||||
|
py_type = self.TYPE_MAP.get(expected_type)
|
||||||
|
if py_type and not isinstance(value, py_type):
|
||||||
|
missing.append(
|
||||||
|
MissingParam(
|
||||||
|
name=name,
|
||||||
|
type=expected_type,
|
||||||
|
description=f"Invalid type, expected {expected_type}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# enum 校验
|
||||||
|
if enum and value not in enum:
|
||||||
|
missing.append(
|
||||||
|
MissingParam(
|
||||||
|
name=name,
|
||||||
|
type=expected_type,
|
||||||
|
description=f"Invalid value, must be one of {enum}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
validated[name] = value
|
||||||
|
|
||||||
|
if missing:
|
||||||
|
return MissingParams(missing=missing)
|
||||||
|
|
||||||
|
return ResolvedParams(params=validated)
|
||||||
|
|
||||||
@ -4,6 +4,7 @@ import yaml
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
from appPublic.dictObject import DictObject
|
from appPublic.dictObject import DictObject
|
||||||
|
from appPublic.log import debug, exception
|
||||||
|
|
||||||
def find_missing_params(
|
def find_missing_params(
|
||||||
input_schema: Dict[str, Any],
|
input_schema: Dict[str, Any],
|
||||||
@ -39,7 +40,7 @@ def load_schemas(path) -> Dict[str, Any]:
|
|||||||
with path.open("r", encoding="utf-8") as f:
|
with path.open("r", encoding="utf-8") as f:
|
||||||
data = yaml.safe_load(f)
|
data = yaml.safe_load(f)
|
||||||
|
|
||||||
return DictObject(data)
|
return DictObject(**data)
|
||||||
|
|
||||||
class SkillkitWrapper:
|
class SkillkitWrapper:
|
||||||
def __init__(self, user_skillsroot, sys_skillsroot=None):
|
def __init__(self, user_skillsroot, sys_skillsroot=None):
|
||||||
@ -54,22 +55,24 @@ class SkillkitWrapper:
|
|||||||
|
|
||||||
def load_skill(self, skill_name):
|
def load_skill(self, skill_name):
|
||||||
skill = self.client.load_skill(skill_name)
|
skill = self.client.load_skill(skill_name)
|
||||||
print(skill, dir(skill))
|
|
||||||
schemaspath = skill.base_directory / 'schemas.yaml'
|
schemaspath = skill.base_directory / 'schemas.yaml'
|
||||||
if schemaspath.exists():
|
if schemaspath.exists():
|
||||||
if not self.schemas.get(skill_name):
|
if not self.schemas.get(skill_name):
|
||||||
data = load_schemas(schemaspath)
|
data = load_schemas(schemaspath)
|
||||||
self.schemas[skill_name] = data
|
self.schemas[skill_name] = data
|
||||||
print(f'{data=}, {str(schemaspath)}')
|
|
||||||
|
|
||||||
return skill
|
return skill
|
||||||
|
|
||||||
def get_script_params(self, skill_name, script_name):
|
def get_script_schema(self, skill_name, script_name):
|
||||||
skill = self.load_skill(skill_name)
|
skill = self.load_skill(skill_name)
|
||||||
d = self.schemas.get('skill_name')
|
d = self.schemas.get(skill_name)
|
||||||
if not d:
|
if not d:
|
||||||
return []
|
# debug(f'{skill_name=}, {self.schemas=} ,has not schemas.yaml')
|
||||||
m = d.scripts.get(script_name)
|
return None
|
||||||
|
return d.scripts.get(script_name)
|
||||||
|
|
||||||
|
def get_script_params(self, skill_name, script_name):
|
||||||
|
m = self.get_script_schema(skill_name, script_name)
|
||||||
if not m:
|
if not m:
|
||||||
return []
|
return []
|
||||||
return m.inputs
|
return m.inputs
|
||||||
@ -85,6 +88,5 @@ class SkillkitWrapper:
|
|||||||
arguments=args
|
arguments=args
|
||||||
)
|
)
|
||||||
def invoke_skill(self, skill_name: str, script_name: str, params: dict):
|
def invoke_skill(self, skill_name: str, script_name: str, params: dict):
|
||||||
print(f"Invoking skill={skill_nmae}, script={script_nmae}, params={params}")
|
|
||||||
return self.client.invoke_skill(skill_nmae, params)
|
return self.client.invoke_skill(skill_nmae, params)
|
||||||
|
|
||||||
|
|||||||
184
skillagent/tmpls.py
Normal file
184
skillagent/tmpls.py
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
choose_candidates_tmpl = {
|
||||||
|
"en":"""
|
||||||
|
User request:
|
||||||
|
\"\"\"{{prompt}}\"\"\"
|
||||||
|
|
||||||
|
Candidate skills:
|
||||||
|
{{spec_desc}}
|
||||||
|
|
||||||
|
Task:
|
||||||
|
1. Choose the best skill with scripts.
|
||||||
|
2. Extract parameters strictly matching schema.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- If a required parameter is missing, set it to null.
|
||||||
|
- Output JSON only.
|
||||||
|
|
||||||
|
Output:
|
||||||
|
{
|
||||||
|
"skill": "...",
|
||||||
|
"script": "...",
|
||||||
|
"params": { ... },
|
||||||
|
"reason": "..."
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
"zh":"""
|
||||||
|
用户需求:
|
||||||
|
\"\"\"{{promot}}\"\"\"
|
||||||
|
|
||||||
|
候选技能脚本:
|
||||||
|
技能|技能描述|脚本|脚本描述|已知参数|缺失参数
|
||||||
|
{% for c in candidates %}
|
||||||
|
{{c.skill}}|{{c.skill_desc}}|{{c.script}}|{{c.script_desc}}|{{json.dumps(c.params, ensure_ascii=False)}}|{{json.dumps(c.missing_params, ensure_ascii=False)}}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
任务:
|
||||||
|
1. 选择最符合用户需求的技能脚本
|
||||||
|
|
||||||
|
规则
|
||||||
|
- 如果一个需要的参数缺失,设为null
|
||||||
|
- 只输出JSON
|
||||||
|
|
||||||
|
输出:
|
||||||
|
{
|
||||||
|
"skill": "...",
|
||||||
|
"script": "...",
|
||||||
|
"params": {},
|
||||||
|
"reason": "..."
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
resume_tmpl = {
|
||||||
|
"en":"""
|
||||||
|
You are an agent helping a user fill parameters for a skill.
|
||||||
|
|
||||||
|
Skill name: {{state.skill}}
|
||||||
|
Script name: {{state.script}}
|
||||||
|
{% set schema = kit.get_script_params(state.skill, state.script) %}
|
||||||
|
{% if schema %}
|
||||||
|
Skill required parameters: {{json.dumps(schema.inputs, ensure_ascii=False)}}
|
||||||
|
{% else %}
|
||||||
|
Skill required parameters:[]
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
User original intent:
|
||||||
|
\"\"\"{{state.user_intent}}\"\"\"
|
||||||
|
|
||||||
|
Known parameters:
|
||||||
|
{{json.dumps(state.params, ensure_ascii=False)}}
|
||||||
|
|
||||||
|
Missing parameters:
|
||||||
|
{{json.dumps(state.missing, ensure_ascii=False)}}
|
||||||
|
|
||||||
|
User reply:
|
||||||
|
\"\"\"{{user_reply}}\"\"\"
|
||||||
|
|
||||||
|
Task:
|
||||||
|
- Extract values ONLY for missing parameters.
|
||||||
|
- Do NOT modify existing known parameters.
|
||||||
|
- All output must match the skill parameter schema.
|
||||||
|
- Output JSON only with the missing parameters.
|
||||||
|
""",
|
||||||
|
"zh":"""
|
||||||
|
你是个帮助技能脚本填参的助手
|
||||||
|
技能名:{{state.skill}}
|
||||||
|
脚本名:{{state.script}}
|
||||||
|
{% set schema = kit.get_script_params(state.skill, state.script) %}
|
||||||
|
{% if schema %}
|
||||||
|
脚本需要的参数:{{json.dumps(schema.inputs, ensure_ascii=False)}}
|
||||||
|
{% else %}
|
||||||
|
脚本不需要的参数
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
用户原始需求:
|
||||||
|
\"\"\"{{state.user_intent}}\"\"\"
|
||||||
|
|
||||||
|
已有的参数:
|
||||||
|
{{json.dumps(state.params, ensure_ascii=False)}}
|
||||||
|
|
||||||
|
缺失参数:
|
||||||
|
{{json.dumps(state.missing, ensure_ascii=False)}}
|
||||||
|
|
||||||
|
用户回复:
|
||||||
|
\"\"\"{{user_reply}}\"\"\"
|
||||||
|
|
||||||
|
任务:
|
||||||
|
- 仅抽取缺失参数的值
|
||||||
|
- 不要修改已知参数的值
|
||||||
|
- 所有输出必须符合技能脚本的参数要求
|
||||||
|
- 值输出缺失参数JSON
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
three_candidates_tmpl = {
|
||||||
|
"en":"""
|
||||||
|
User request:
|
||||||
|
\"\"\"{{json.dumps(prompt, ensure_ascii=False)}}\"\"\"
|
||||||
|
|
||||||
|
Available skills:
|
||||||
|
{{skill_list}}
|
||||||
|
|
||||||
|
Task:
|
||||||
|
Select up to 3 most relevant skill's scripts.
|
||||||
|
|
||||||
|
Output JSON list only.
|
||||||
|
""",
|
||||||
|
"zh":"""
|
||||||
|
用户需求:
|
||||||
|
\"\"\"{{json.dumps(prompt, ensure_ascii=False)}}\"\"\"
|
||||||
|
|
||||||
|
可用的技能脚本:
|
||||||
|
{% for s in skills %}
|
||||||
|
名字:{{s.metadata.name}}
|
||||||
|
描述:{{s.metadata.description}}
|
||||||
|
{% if s.scripts %}
|
||||||
|
脚本:
|
||||||
|
{% for sc in s.scripts %}
|
||||||
|
名字:{{sc.name}}
|
||||||
|
{% set schema = kit.get_script_schema(s.metadata.name, sc.name) %}
|
||||||
|
{% if schema %}
|
||||||
|
描述:{{schema.description or sc.description}}
|
||||||
|
参数:{{json.dumps(schema.params, ensure_ascii=False)}}
|
||||||
|
{% else %}
|
||||||
|
描述:{{sc.description}}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
任务:
|
||||||
|
- 选择3个相关的技能脚本
|
||||||
|
- 按照脚本参数要求,为脚本从用户需求中识别出参数
|
||||||
|
|
||||||
|
输出
|
||||||
|
仅输出json,每个元素为
|
||||||
|
{
|
||||||
|
"skill":技能名字,
|
||||||
|
"skill_desc":技能描述(很重要不能缺失)
|
||||||
|
"script":脚本名字,
|
||||||
|
“script_desc”:脚本描述(很重要不能缺失)
|
||||||
|
“params”:参数字典,key为参数名,value为参数值
|
||||||
|
“missing_params”:如果有缺失参数,将缺失参数的参数名放在这数组里
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
ask_user_reply_tmpl = {
|
||||||
|
"en":"""
|
||||||
|
User request:
|
||||||
|
\"\"\"{{user_text}}\"\"\"
|
||||||
|
|
||||||
|
The script "{{script}} in skill "{{skill}}" requires the following missing parameters:
|
||||||
|
{{json.dumps(fields, ensure_ascii=False)}}
|
||||||
|
|
||||||
|
Ask the user a concise clarification question.
|
||||||
|
""",
|
||||||
|
"zh":"""
|
||||||
|
用户需求
|
||||||
|
\"\"\"{{user_text}}\"\"\"
|
||||||
|
|
||||||
|
这个技能“{{skill}}“下的脚本”{{script}}“需要这些参数:
|
||||||
|
{{json.dumps(fields, ensure_ascii=False)}}
|
||||||
|
请用户继续完善问题
|
||||||
|
"""
|
||||||
|
}
|
||||||
52
test/skills/calculator/SKILL.md
Normal file
52
test/skills/calculator/SKILL.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
name: calculator
|
||||||
|
description: "Perform basic arithmetic operations: addition, subtraction, multiplication, and division"
|
||||||
|
---
|
||||||
|
|
||||||
|
# calculator
|
||||||
|
|
||||||
|
Perform basic arithmetic operations: addition, subtraction, multiplication, and division.
|
||||||
|
|
||||||
|
## Input
|
||||||
|
|
||||||
|
The input is a JSON object provided via standard input:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"a": 10,
|
||||||
|
"op": "+",
|
||||||
|
"b": 5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
- `a` (number, required): First operand
|
||||||
|
- `op` (string, required): Operator, one of `+`, `-`, `*`, `/`
|
||||||
|
- `b` (number, required): Second operand
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
Returns a JSON object to standard output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"result": 15
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
On failure, returns a JSON object:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "division by zero"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- This skill is non-interactive.
|
||||||
|
- Output is always valid JSON.
|
||||||
|
- No output is written to stderr.
|
||||||
31
test/skills/calculator/run.sh
Executable file
31
test/skills/calculator/run.sh
Executable file
@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
read -r INPUT
|
||||||
|
|
||||||
|
echo "$INPUT"
|
||||||
|
error() {
|
||||||
|
echo "{\"error\":\"$1\"}"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
A=$(echo "$INPUT" | jq -r '.a // empty')
|
||||||
|
OP=$(echo "$INPUT" | jq -r '.op // empty')
|
||||||
|
B=$(echo "$INPUT" | jq -r '.b // empty')
|
||||||
|
|
||||||
|
[[ -z "$A" || -z "$OP" || -z "$B" ]] && error "missing parameter"
|
||||||
|
|
||||||
|
is_number='^-?[0-9]+([.][0-9]+)?$'
|
||||||
|
[[ ! "$A" =~ $is_number ]] && error "a is not a number"
|
||||||
|
[[ ! "$B" =~ $is_number ]] && error "b is not a number"
|
||||||
|
|
||||||
|
case "$OP" in
|
||||||
|
"+") RESULT=$(echo "$A + $B" | bc) ;;
|
||||||
|
"-") RESULT=$(echo "$A - $B" | bc) ;;
|
||||||
|
"*") RESULT=$(echo "$A * $B" | bc) ;;
|
||||||
|
"/")
|
||||||
|
[[ "$(echo "$B == 0" | bc)" -eq 1 ]] && error "division by zero"
|
||||||
|
RESULT=$(echo "scale=10; $A / $B" | bc)
|
||||||
|
;;
|
||||||
|
*) error "unsupported operator" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "{\"result\": $RESULT}"
|
||||||
18
test/skills/calculator/schemas.yaml
Normal file
18
test/skills/calculator/schemas.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
scripts:
|
||||||
|
run:
|
||||||
|
description: 四则运算
|
||||||
|
inputs:
|
||||||
|
a:
|
||||||
|
type: int or float
|
||||||
|
required: true
|
||||||
|
description: 计算左值
|
||||||
|
op:
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
description: 计算方法
|
||||||
|
enum: [+ - * /]
|
||||||
|
b:
|
||||||
|
type: int or float
|
||||||
|
required: true
|
||||||
|
description: 计算左值
|
||||||
|
|
||||||
37
test/skills/code-reviewer/SKILL.md
Normal file
37
test/skills/code-reviewer/SKILL.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
name: code-reviewer
|
||||||
|
description: Review code for best practices, potential bugs, security vulnerabilities, and performance issues
|
||||||
|
allowed-tools: Read, Grep
|
||||||
|
---
|
||||||
|
|
||||||
|
# Code Reviewer Skill
|
||||||
|
|
||||||
|
You are an experienced code reviewer. Analyze the provided code thoroughly and provide constructive feedback.
|
||||||
|
|
||||||
|
## Review Areas
|
||||||
|
|
||||||
|
1. **Best Practices**: Check adherence to language-specific conventions
|
||||||
|
2. **Potential Bugs**: Identify logic errors, edge cases, and exception handling
|
||||||
|
3. **Security**: Look for common vulnerabilities (SQL injection, XSS, etc.)
|
||||||
|
4. **Performance**: Spot inefficient algorithms or resource usage
|
||||||
|
5. **Readability**: Assess code clarity, naming, and documentation
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
Provide your review in the following structure:
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
Brief overview of the code quality
|
||||||
|
|
||||||
|
### Issues Found
|
||||||
|
List specific issues with severity (Critical/High/Medium/Low) and location
|
||||||
|
|
||||||
|
### Recommendations
|
||||||
|
Actionable suggestions for improvement
|
||||||
|
|
||||||
|
### Positive Aspects
|
||||||
|
Highlight what was done well
|
||||||
32
test/skills/code-reviewer/run.sh
Executable file
32
test/skills/code-reviewer/run.sh
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
read -r INPUT
|
||||||
|
|
||||||
|
error() {
|
||||||
|
echo "{\"error\":\"$1\"}"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
A=$(echo "$INPUT" | jq -r '.a // empty')
|
||||||
|
OP=$(echo "$INPUT" | jq -r '.op // empty')
|
||||||
|
B=$(echo "$INPUT" | jq -r '.b // empty')
|
||||||
|
|
||||||
|
[[ -z "$A" || -z "$OP" || -z "$B" ]] && error "missing parameter"
|
||||||
|
|
||||||
|
is_number='^-?[0-9]+([.][0-9]+)?$'
|
||||||
|
[[ ! "$A" =~ $is_number ]] && error "a is not a number"
|
||||||
|
[[ ! "$B" =~ $is_number ]] && error "b is not a number"
|
||||||
|
|
||||||
|
case "$OP" in
|
||||||
|
"+") RESULT=$(echo "$A + $B" | bc) ;;
|
||||||
|
"-") RESULT=$(echo "$A - $B" | bc) ;;
|
||||||
|
"*") RESULT=$(echo "$A * $B" | bc) ;;
|
||||||
|
"/")
|
||||||
|
[[ "$(echo "$B == 0" | bc)" -eq 1 ]] && error "division by zero"
|
||||||
|
RESULT=$(echo "scale=10; $A / $B" | bc)
|
||||||
|
;;
|
||||||
|
*) error "unsupported operator" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "{\"result\": $RESULT}"
|
||||||
18
test/skills/example-plugin/.claude-plugin/plugin.json
Normal file
18
test/skills/example-plugin/.claude-plugin/plugin.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": "0.1",
|
||||||
|
"name": "example-plugin",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Example plugin demonstrating plugin manifest structure with multiple skill directories",
|
||||||
|
"author": {
|
||||||
|
"name": "Skillkit Team",
|
||||||
|
"email": "team@skillkit.example.com",
|
||||||
|
"url": "https://github.com/skillkit/example-plugin"
|
||||||
|
},
|
||||||
|
"skills": ["skills/"],
|
||||||
|
"display_name": "Example Plugin",
|
||||||
|
"homepage": "https://github.com/skillkit/example-plugin",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/skillkit/example-plugin"
|
||||||
|
}
|
||||||
|
}
|
||||||
38
test/skills/example-plugin/skills/csv-parser/SKILL.md
Normal file
38
test/skills/example-plugin/skills/csv-parser/SKILL.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
name: csv-parser
|
||||||
|
description: Parse and analyze CSV files with data validation
|
||||||
|
---
|
||||||
|
|
||||||
|
# CSV Parser Skill
|
||||||
|
|
||||||
|
You are a CSV file analysis assistant from the example-plugin.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
- Parse CSV files with various delimiters
|
||||||
|
- Validate data types and constraints
|
||||||
|
- Generate summary statistics
|
||||||
|
- Detect encoding issues
|
||||||
|
- Handle malformed data gracefully
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To analyze a CSV file, provide the file path as an argument:
|
||||||
|
|
||||||
|
```
|
||||||
|
Arguments: $ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
The analysis will include:
|
||||||
|
- Row count and column count
|
||||||
|
- Column names and inferred data types
|
||||||
|
- Missing value report
|
||||||
|
- Basic statistics for numeric columns
|
||||||
|
- Encoding and delimiter detection results
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Input: data.csv
|
||||||
|
Output: Analysis report with statistics and validation results
|
||||||
37
test/skills/example-plugin/skills/json-parser/SKILL.md
Normal file
37
test/skills/example-plugin/skills/json-parser/SKILL.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
name: json-parser
|
||||||
|
description: Parse and validate JSON data with schema support
|
||||||
|
---
|
||||||
|
|
||||||
|
# JSON Parser Skill
|
||||||
|
|
||||||
|
You are a JSON data validation assistant from the example-plugin.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
- Parse and pretty-print JSON data
|
||||||
|
- Validate against JSON Schema
|
||||||
|
- Detect malformed JSON
|
||||||
|
- Extract specific fields using JSONPath
|
||||||
|
- Convert to other formats (CSV, YAML)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To parse JSON data, provide the file path or raw JSON as an argument:
|
||||||
|
|
||||||
|
```
|
||||||
|
Arguments: $ARGUMENTS
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
The parser will provide:
|
||||||
|
- Validation status (valid/invalid)
|
||||||
|
- Structure overview (depth, object count)
|
||||||
|
- Schema compliance report (if schema provided)
|
||||||
|
- Extracted values (if JSONPath provided)
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Input: config.json
|
||||||
|
Output: Validated JSON structure with compliance report
|
||||||
68
test/skills/file-reference-skill/SKILL.md
Normal file
68
test/skills/file-reference-skill/SKILL.md
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
name: file-reference-skill
|
||||||
|
description: Example skill demonstrating secure file reference resolution with supporting files
|
||||||
|
allowed-tools: []
|
||||||
|
---
|
||||||
|
|
||||||
|
# File Reference Skill
|
||||||
|
|
||||||
|
This skill demonstrates how to use supporting files (scripts, templates, documentation) within a skill directory.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This skill uses helper scripts and templates for data processing. All supporting files are accessible via relative paths from the skill's base directory.
|
||||||
|
|
||||||
|
## Available Supporting Files
|
||||||
|
|
||||||
|
### Scripts
|
||||||
|
- `scripts/data_processor.py` - Main data processing script
|
||||||
|
- `scripts/validator.py` - Input validation utilities
|
||||||
|
- `scripts/helper.sh` - Shell helper script
|
||||||
|
|
||||||
|
### Templates
|
||||||
|
- `templates/config.yaml` - Configuration template
|
||||||
|
- `templates/report.md` - Report generation template
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- `docs/usage.md` - Detailed usage instructions
|
||||||
|
- `docs/examples.md` - Example use cases
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
When this skill is invoked with arguments, it can access supporting files using the FilePathResolver:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pathlib import Path
|
||||||
|
from skillkit.core.path_resolver import FilePathResolver
|
||||||
|
|
||||||
|
# Get the skill's base directory (injected by BaseDirectoryProcessor)
|
||||||
|
base_dir = Path("<base_directory_from_context>")
|
||||||
|
|
||||||
|
# Resolve supporting files securely
|
||||||
|
processor_script = FilePathResolver.resolve_path(base_dir, "scripts/data_processor.py")
|
||||||
|
config_template = FilePathResolver.resolve_path(base_dir, "templates/config.yaml")
|
||||||
|
usage_docs = FilePathResolver.resolve_path(base_dir, "docs/usage.md")
|
||||||
|
|
||||||
|
# Read file contents
|
||||||
|
with open(processor_script) as f:
|
||||||
|
script_code = f.read()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Processing Arguments
|
||||||
|
|
||||||
|
The skill expects data file paths as arguments:
|
||||||
|
|
||||||
|
**Example invocation**: `file-reference-skill data/input.csv data/output.csv`
|
||||||
|
|
||||||
|
Processing steps:
|
||||||
|
1. Validate input using `scripts/validator.py`
|
||||||
|
2. Process data using `scripts/data_processor.py`
|
||||||
|
3. Generate report using `templates/report.md`
|
||||||
|
4. Output results to specified location
|
||||||
|
|
||||||
|
## Security Notes
|
||||||
|
|
||||||
|
- All file paths are validated to prevent directory traversal attacks
|
||||||
|
- Symlinks are resolved and verified to stay within skill directory
|
||||||
|
- Absolute paths and path traversal patterns (../) are blocked
|
||||||
|
- Any security violation raises PathSecurityError with detailed logging
|
||||||
262
test/skills/file-reference-skill/docs/examples.md
Normal file
262
test/skills/file-reference-skill/docs/examples.md
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
# File Reference Skill - Examples
|
||||||
|
|
||||||
|
## Example 1: Simple Data Processing
|
||||||
|
|
||||||
|
Process a CSV file using the skill's data processor:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from skillkit import SkillManager
|
||||||
|
from skillkit.core.path_resolver import FilePathResolver
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Initialize skill manager
|
||||||
|
manager = SkillManager("./examples/skills")
|
||||||
|
manager.discover()
|
||||||
|
|
||||||
|
# Invoke skill
|
||||||
|
result = manager.invoke_skill(
|
||||||
|
"file-reference-skill",
|
||||||
|
"data/input.csv data/output.csv"
|
||||||
|
)
|
||||||
|
|
||||||
|
print(result)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example 2: Accessing Supporting Scripts
|
||||||
|
|
||||||
|
Read and execute supporting scripts:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pathlib import Path
|
||||||
|
from skillkit.core.path_resolver import FilePathResolver
|
||||||
|
|
||||||
|
# Get skill's base directory
|
||||||
|
skill = manager.get_skill("file-reference-skill")
|
||||||
|
base_dir = skill.base_directory
|
||||||
|
|
||||||
|
# Resolve script path securely
|
||||||
|
processor_path = FilePathResolver.resolve_path(
|
||||||
|
base_dir,
|
||||||
|
"scripts/data_processor.py"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Read script content
|
||||||
|
with open(processor_path) as f:
|
||||||
|
script_code = f.read()
|
||||||
|
|
||||||
|
print(f"Script location: {processor_path}")
|
||||||
|
print(f"Script length: {len(script_code)} bytes")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example 3: Loading Configuration Template
|
||||||
|
|
||||||
|
Load and parse configuration template:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import yaml
|
||||||
|
from skillkit.core.path_resolver import FilePathResolver
|
||||||
|
|
||||||
|
# Resolve config template path
|
||||||
|
config_path = FilePathResolver.resolve_path(
|
||||||
|
base_dir,
|
||||||
|
"templates/config.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load configuration
|
||||||
|
with open(config_path) as f:
|
||||||
|
config = yaml.safe_load(f)
|
||||||
|
|
||||||
|
print("Configuration:", config)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example 4: Handling Security Violations
|
||||||
|
|
||||||
|
Demonstrate path traversal prevention:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from skillkit.core.path_resolver import FilePathResolver
|
||||||
|
from skillkit.core.exceptions import PathSecurityError
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Attempt path traversal (will be blocked)
|
||||||
|
malicious_path = FilePathResolver.resolve_path(
|
||||||
|
base_dir,
|
||||||
|
"../../../etc/passwd"
|
||||||
|
)
|
||||||
|
except PathSecurityError as e:
|
||||||
|
print(f"Security violation blocked: {e}")
|
||||||
|
# Expected output:
|
||||||
|
# Security violation blocked: Path traversal attempt detected:
|
||||||
|
# '../../../etc/passwd' resolves outside skill directory
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example 5: Validating Input Files
|
||||||
|
|
||||||
|
Use validator script to check input files:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import subprocess
|
||||||
|
from skillkit.core.path_resolver import FilePathResolver
|
||||||
|
|
||||||
|
# Resolve validator script
|
||||||
|
validator_path = FilePathResolver.resolve_path(
|
||||||
|
base_dir,
|
||||||
|
"scripts/validator.py"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Import and use validator
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, str(validator_path.parent))
|
||||||
|
from validator import validate_csv_format
|
||||||
|
|
||||||
|
# Validate input file
|
||||||
|
is_valid = validate_csv_format("data/input.csv")
|
||||||
|
print(f"File is valid: {is_valid}")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example 6: Generating Reports
|
||||||
|
|
||||||
|
Generate report using template:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from string import Template
|
||||||
|
from datetime import datetime
|
||||||
|
from skillkit.core.path_resolver import FilePathResolver
|
||||||
|
|
||||||
|
# Resolve report template
|
||||||
|
template_path = FilePathResolver.resolve_path(
|
||||||
|
base_dir,
|
||||||
|
"templates/report.md"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load template
|
||||||
|
with open(template_path) as f:
|
||||||
|
template_content = f.read()
|
||||||
|
|
||||||
|
# Fill template with data
|
||||||
|
template = Template(template_content)
|
||||||
|
report = template.safe_substitute({
|
||||||
|
'timestamp': datetime.now().isoformat(),
|
||||||
|
'input_file': 'data/input.csv',
|
||||||
|
'input_size': '1234',
|
||||||
|
'format': 'CSV',
|
||||||
|
'encoding': 'UTF-8',
|
||||||
|
'start_time': '10:00:00',
|
||||||
|
'end_time': '10:00:05',
|
||||||
|
'duration': '5',
|
||||||
|
'status': 'SUCCESS',
|
||||||
|
'output_file': 'data/output.csv',
|
||||||
|
'output_size': '1234',
|
||||||
|
'record_count': '100',
|
||||||
|
'error_count': '0',
|
||||||
|
'validation_results': 'All checks passed',
|
||||||
|
'processing_log': 'Processing completed successfully'
|
||||||
|
})
|
||||||
|
|
||||||
|
print(report)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example 7: Shell Script Integration
|
||||||
|
|
||||||
|
Execute shell helper script:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import subprocess
|
||||||
|
from skillkit.core.path_resolver import FilePathResolver
|
||||||
|
|
||||||
|
# Resolve shell script
|
||||||
|
helper_path = FilePathResolver.resolve_path(
|
||||||
|
base_dir,
|
||||||
|
"scripts/helper.sh"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Execute script
|
||||||
|
result = subprocess.run(
|
||||||
|
['bash', str(helper_path), 'check'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
print(result.stdout)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example 8: Multiple File Access
|
||||||
|
|
||||||
|
Access multiple supporting files in one operation:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from skillkit.core.path_resolver import FilePathResolver
|
||||||
|
|
||||||
|
# List of files to access
|
||||||
|
file_paths = [
|
||||||
|
"scripts/data_processor.py",
|
||||||
|
"scripts/validator.py",
|
||||||
|
"templates/config.yaml",
|
||||||
|
"docs/usage.md"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Resolve all paths securely
|
||||||
|
resolved_paths = {}
|
||||||
|
for rel_path in file_paths:
|
||||||
|
try:
|
||||||
|
abs_path = FilePathResolver.resolve_path(base_dir, rel_path)
|
||||||
|
resolved_paths[rel_path] = abs_path
|
||||||
|
print(f"✓ {rel_path} -> {abs_path}")
|
||||||
|
except PathSecurityError as e:
|
||||||
|
print(f"✗ {rel_path} -> BLOCKED ({e})")
|
||||||
|
|
||||||
|
print(f"\nSuccessfully resolved {len(resolved_paths)} paths")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example 9: Error Handling Best Practices
|
||||||
|
|
||||||
|
Robust error handling when accessing supporting files:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pathlib import Path
|
||||||
|
from skillkit.core.path_resolver import FilePathResolver
|
||||||
|
from skillkit.core.exceptions import PathSecurityError
|
||||||
|
|
||||||
|
def safe_load_supporting_file(base_dir: Path, rel_path: str) -> str:
|
||||||
|
"""Safely load supporting file with comprehensive error handling."""
|
||||||
|
try:
|
||||||
|
# Resolve path securely
|
||||||
|
abs_path = FilePathResolver.resolve_path(base_dir, rel_path)
|
||||||
|
|
||||||
|
# Read file content
|
||||||
|
with open(abs_path, 'r', encoding='utf-8') as f:
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
except PathSecurityError as e:
|
||||||
|
print(f"Security violation: {e}")
|
||||||
|
raise
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"File not found: {rel_path}")
|
||||||
|
raise
|
||||||
|
except PermissionError:
|
||||||
|
print(f"Permission denied: {rel_path}")
|
||||||
|
raise
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
print(f"Invalid UTF-8 encoding: {rel_path}")
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Unexpected error loading {rel_path}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
try:
|
||||||
|
content = safe_load_supporting_file(base_dir, "scripts/helper.py")
|
||||||
|
print(f"Loaded {len(content)} bytes")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to load file: {e}")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
These examples demonstrate:
|
||||||
|
- Secure file path resolution using FilePathResolver
|
||||||
|
- Accessing scripts, templates, and documentation
|
||||||
|
- Handling security violations gracefully
|
||||||
|
- Integration with Python and shell scripts
|
||||||
|
- Best practices for error handling
|
||||||
|
- Template-based report generation
|
||||||
141
test/skills/file-reference-skill/docs/usage.md
Normal file
141
test/skills/file-reference-skill/docs/usage.md
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
# File Reference Skill - Usage Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The file-reference-skill demonstrates how to structure a skill with supporting files (scripts, templates, documentation) and access them securely using the FilePathResolver.
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
file-reference-skill/
|
||||||
|
├── SKILL.md # Main skill definition
|
||||||
|
├── scripts/ # Processing scripts
|
||||||
|
│ ├── data_processor.py # Main data processor
|
||||||
|
│ ├── validator.py # Input validation
|
||||||
|
│ └── helper.sh # Shell utilities
|
||||||
|
├── templates/ # Configuration and output templates
|
||||||
|
│ ├── config.yaml # Configuration template
|
||||||
|
│ └── report.md # Report generation template
|
||||||
|
└── docs/ # Documentation
|
||||||
|
├── usage.md # This file
|
||||||
|
└── examples.md # Example use cases
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Supporting Files
|
||||||
|
|
||||||
|
### From Python
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pathlib import Path
|
||||||
|
from skillkit.core.path_resolver import FilePathResolver
|
||||||
|
|
||||||
|
# Base directory is provided in the skill context
|
||||||
|
base_dir = Path("/path/to/skills/file-reference-skill")
|
||||||
|
|
||||||
|
# Resolve paths securely
|
||||||
|
processor_path = FilePathResolver.resolve_path(
|
||||||
|
base_dir,
|
||||||
|
"scripts/data_processor.py"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Read file content
|
||||||
|
with open(processor_path) as f:
|
||||||
|
script_code = f.read()
|
||||||
|
```
|
||||||
|
|
||||||
|
### From Shell
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get base directory from skill context
|
||||||
|
BASE_DIR="/path/to/skills/file-reference-skill"
|
||||||
|
|
||||||
|
# Use helper script
|
||||||
|
bash "$BASE_DIR/scripts/helper.sh" check
|
||||||
|
|
||||||
|
# Run data processor
|
||||||
|
python3 "$BASE_DIR/scripts/data_processor.py" input.csv output.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Features
|
||||||
|
|
||||||
|
The FilePathResolver ensures:
|
||||||
|
|
||||||
|
1. **Path Traversal Prevention**: Blocks attempts to access files outside skill directory
|
||||||
|
2. **Symlink Validation**: Resolves symlinks and verifies targets stay within base directory
|
||||||
|
3. **Absolute Path Rejection**: Prevents absolute path injection
|
||||||
|
4. **Detailed Logging**: All security violations logged at ERROR level
|
||||||
|
|
||||||
|
### Valid Paths
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Allowed - relative path within skill directory
|
||||||
|
FilePathResolver.resolve_path(base_dir, "scripts/helper.py")
|
||||||
|
FilePathResolver.resolve_path(base_dir, "templates/config.yaml")
|
||||||
|
FilePathResolver.resolve_path(base_dir, "docs/usage.md")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Invalid Paths (Blocked)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Blocked - directory traversal
|
||||||
|
FilePathResolver.resolve_path(base_dir, "../../etc/passwd")
|
||||||
|
|
||||||
|
# Blocked - absolute path
|
||||||
|
FilePathResolver.resolve_path(base_dir, "/etc/passwd")
|
||||||
|
|
||||||
|
# Blocked - symlink escape
|
||||||
|
# (if symlink target is outside base_dir)
|
||||||
|
FilePathResolver.resolve_path(base_dir, "malicious_link")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Workflow
|
||||||
|
|
||||||
|
1. **Skill Invocation**
|
||||||
|
```python
|
||||||
|
manager = SkillManager()
|
||||||
|
manager.discover()
|
||||||
|
|
||||||
|
result = manager.invoke_skill(
|
||||||
|
"file-reference-skill",
|
||||||
|
"input_data.csv output_data.csv"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Skill Processing**
|
||||||
|
- Skill receives base directory in context
|
||||||
|
- Script paths resolved using FilePathResolver
|
||||||
|
- Scripts executed with validated paths
|
||||||
|
- Results returned to caller
|
||||||
|
|
||||||
|
3. **File Access**
|
||||||
|
- All file operations use resolved paths
|
||||||
|
- Security violations raise PathSecurityError
|
||||||
|
- Detailed error messages help debugging
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Always use FilePathResolver** for accessing supporting files
|
||||||
|
2. **Use relative paths** from skill base directory
|
||||||
|
3. **Document file dependencies** in SKILL.md
|
||||||
|
4. **Test with various path patterns** including edge cases
|
||||||
|
5. **Handle PathSecurityError** appropriately in your code
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### PathSecurityError
|
||||||
|
|
||||||
|
**Problem**: Attempting to access files outside skill directory
|
||||||
|
|
||||||
|
**Solution**: Use relative paths within skill directory only
|
||||||
|
|
||||||
|
### FileNotFoundError
|
||||||
|
|
||||||
|
**Problem**: Resolved path doesn't exist
|
||||||
|
|
||||||
|
**Solution**: Verify file exists in skill directory structure
|
||||||
|
|
||||||
|
### PermissionError
|
||||||
|
|
||||||
|
**Problem**: Cannot read resolved file
|
||||||
|
|
||||||
|
**Solution**: Check file permissions and ownership
|
||||||
51
test/skills/file-reference-skill/scripts/data_processor.py
Normal file
51
test/skills/file-reference-skill/scripts/data_processor.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
"""Data processing script for file-reference-skill.
|
||||||
|
|
||||||
|
This script demonstrates how supporting files can be used within a skill.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def process_data(input_file: str, output_file: str) -> None:
|
||||||
|
"""Process data from input file and write to output file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_file: Path to input data file
|
||||||
|
output_file: Path to output data file
|
||||||
|
"""
|
||||||
|
print(f"Processing data from {input_file}")
|
||||||
|
print(f"Output will be written to {output_file}")
|
||||||
|
|
||||||
|
# Read input file
|
||||||
|
try:
|
||||||
|
with open(input_file, 'r') as f:
|
||||||
|
data = f.read()
|
||||||
|
print(f"Read {len(data)} bytes from input file")
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: Input file not found: {input_file}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Process data (example: uppercase transformation)
|
||||||
|
processed_data = data.upper()
|
||||||
|
|
||||||
|
# Write output file
|
||||||
|
with open(output_file, 'w') as f:
|
||||||
|
f.write(processed_data)
|
||||||
|
print(f"Wrote {len(processed_data)} bytes to output file")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Main entry point."""
|
||||||
|
if len(sys.argv) != 3:
|
||||||
|
print("Usage: data_processor.py <input_file> <output_file>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
input_file = sys.argv[1]
|
||||||
|
output_file = sys.argv[2]
|
||||||
|
|
||||||
|
process_data(input_file, output_file)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
102
test/skills/file-reference-skill/scripts/env_demo.py
Executable file
102
test/skills/file-reference-skill/scripts/env_demo.py
Executable file
@ -0,0 +1,102 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Environment Variable Demonstration Script
|
||||||
|
|
||||||
|
This script demonstrates how skillkit automatically injects environment
|
||||||
|
variables into script execution context.
|
||||||
|
|
||||||
|
Injected Variables:
|
||||||
|
- SKILL_NAME: Name of the skill
|
||||||
|
- SKILL_BASE_DIR: Absolute path to skill directory
|
||||||
|
- SKILL_VERSION: Version from skill metadata
|
||||||
|
- SKILLKIT_VERSION: Current skillkit version
|
||||||
|
|
||||||
|
These variables can be used for:
|
||||||
|
- Locating files relative to skill directory
|
||||||
|
- Including skill context in logs
|
||||||
|
- Version-specific behavior
|
||||||
|
- Debugging and troubleshooting
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
This script is designed to be executed by skillkit's script executor.
|
||||||
|
It reads JSON arguments from stdin and writes results to stdout.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Demonstrate environment variable access."""
|
||||||
|
# Read arguments from stdin (standard skillkit pattern)
|
||||||
|
try:
|
||||||
|
args = json.load(sys.stdin)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
args = {}
|
||||||
|
|
||||||
|
# Access injected environment variables
|
||||||
|
skill_name = os.environ.get('SKILL_NAME', 'unknown')
|
||||||
|
skill_base = os.environ.get('SKILL_BASE_DIR', 'unknown')
|
||||||
|
skill_version = os.environ.get('SKILL_VERSION', '0.0.0')
|
||||||
|
skillkit_version = os.environ.get('SKILLKIT_VERSION', 'unknown')
|
||||||
|
|
||||||
|
# Prepare output
|
||||||
|
output = {
|
||||||
|
"message": "Environment variables successfully accessed!",
|
||||||
|
"context": {
|
||||||
|
"skill_name": skill_name,
|
||||||
|
"skill_base_dir": skill_base,
|
||||||
|
"skill_version": skill_version,
|
||||||
|
"skillkit_version": skillkit_version
|
||||||
|
},
|
||||||
|
"arguments_received": args,
|
||||||
|
"examples": {
|
||||||
|
"relative_file_path": "Use SKILL_BASE_DIR to locate files",
|
||||||
|
"logging": f"[{skill_name} v{skill_version}] Log message here",
|
||||||
|
"file_resolution": str(Path(skill_base) / "data" / "config.json")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print formatted output
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"Skill: {skill_name} v{skill_version}")
|
||||||
|
print(f"Directory: {skill_base}")
|
||||||
|
print(f"Powered by: skillkit v{skillkit_version}")
|
||||||
|
print("=" * 60)
|
||||||
|
print()
|
||||||
|
print("Environment Variables:")
|
||||||
|
print(f" SKILL_NAME = {skill_name}")
|
||||||
|
print(f" SKILL_BASE_DIR = {skill_base}")
|
||||||
|
print(f" SKILL_VERSION = {skill_version}")
|
||||||
|
print(f" SKILLKIT_VERSION = {skillkit_version}")
|
||||||
|
print()
|
||||||
|
print("Arguments Received:")
|
||||||
|
print(f" {json.dumps(args, indent=2)}")
|
||||||
|
print()
|
||||||
|
print("Example Use Cases:")
|
||||||
|
print(f" 1. Locate skill files:")
|
||||||
|
print(f" config_path = Path(os.environ['SKILL_BASE_DIR']) / 'config.json'")
|
||||||
|
print(f" → {Path(skill_base) / 'config.json'}")
|
||||||
|
print()
|
||||||
|
print(f" 2. Contextual logging:")
|
||||||
|
print(f" logger.info(f'[{{os.environ[\"SKILL_NAME\"]}}] Processing...')")
|
||||||
|
print(f" → [{skill_name}] Processing...")
|
||||||
|
print()
|
||||||
|
print(f" 3. Version-specific behavior:")
|
||||||
|
print(f" if os.environ['SKILL_VERSION'] >= '2.0.0':")
|
||||||
|
print(f" use_new_api()")
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Also output as JSON for programmatic use
|
||||||
|
print()
|
||||||
|
print("JSON Output:")
|
||||||
|
print(json.dumps(output, indent=2))
|
||||||
|
|
||||||
|
# Exit successfully
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
39
test/skills/file-reference-skill/scripts/helper.sh
Executable file
39
test/skills/file-reference-skill/scripts/helper.sh
Executable file
@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Helper script for file-reference-skill
|
||||||
|
|
||||||
|
echo "File Reference Skill Helper Script"
|
||||||
|
echo "==================================="
|
||||||
|
echo ""
|
||||||
|
echo "This script demonstrates shell scripting support in skills."
|
||||||
|
echo ""
|
||||||
|
echo "Usage: ./helper.sh <command> [args...]"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
case "${1:-help}" in
|
||||||
|
check)
|
||||||
|
echo "Checking environment..."
|
||||||
|
echo "Python version: $(python3 --version)"
|
||||||
|
echo "Current directory: $(pwd)"
|
||||||
|
echo "Script directory: $(dirname "$0")"
|
||||||
|
;;
|
||||||
|
validate)
|
||||||
|
if [ -z "$2" ]; then
|
||||||
|
echo "Error: No file specified"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Validating file: $2"
|
||||||
|
if [ -f "$2" ]; then
|
||||||
|
echo "File exists: $2"
|
||||||
|
echo "File size: $(wc -c < "$2") bytes"
|
||||||
|
else
|
||||||
|
echo "File not found: $2"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
help|*)
|
||||||
|
echo "Available commands:"
|
||||||
|
echo " check - Check environment"
|
||||||
|
echo " validate <file> - Validate file exists"
|
||||||
|
echo " help - Show this help message"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
60
test/skills/file-reference-skill/scripts/validator.py
Normal file
60
test/skills/file-reference-skill/scripts/validator.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
"""Input validation utilities for file-reference-skill."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def validate_file_path(file_path: str) -> bool:
|
||||||
|
"""Validate that a file path exists and is readable.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: Path to validate
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if valid, False otherwise
|
||||||
|
"""
|
||||||
|
path = Path(file_path)
|
||||||
|
|
||||||
|
if not path.exists():
|
||||||
|
print(f"Error: File does not exist: {file_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not path.is_file():
|
||||||
|
print(f"Error: Path is not a file: {file_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(path, 'r') as f:
|
||||||
|
f.read(1)
|
||||||
|
return True
|
||||||
|
except PermissionError:
|
||||||
|
print(f"Error: Permission denied reading file: {file_path}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: Cannot read file: {file_path} ({e})")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def validate_csv_format(file_path: str) -> bool:
|
||||||
|
"""Validate that a file is in CSV format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: Path to CSV file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if valid CSV, False otherwise
|
||||||
|
"""
|
||||||
|
if not validate_file_path(file_path):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check file extension
|
||||||
|
if not file_path.endswith('.csv'):
|
||||||
|
print(f"Warning: File does not have .csv extension: {file_path}")
|
||||||
|
|
||||||
|
# Check for CSV content (basic validation)
|
||||||
|
with open(file_path, 'r') as f:
|
||||||
|
first_line = f.readline()
|
||||||
|
if ',' not in first_line:
|
||||||
|
print(f"Warning: File may not be valid CSV (no commas found): {file_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
28
test/skills/file-reference-skill/templates/config.yaml
Normal file
28
test/skills/file-reference-skill/templates/config.yaml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Configuration template for file-reference-skill
|
||||||
|
|
||||||
|
# Data processing settings
|
||||||
|
processing:
|
||||||
|
input_format: csv
|
||||||
|
output_format: csv
|
||||||
|
encoding: utf-8
|
||||||
|
delimiter: ","
|
||||||
|
skip_header: false
|
||||||
|
|
||||||
|
# Validation settings
|
||||||
|
validation:
|
||||||
|
check_encoding: true
|
||||||
|
check_format: true
|
||||||
|
max_file_size_mb: 100
|
||||||
|
required_columns: []
|
||||||
|
|
||||||
|
# Output settings
|
||||||
|
output:
|
||||||
|
include_timestamp: true
|
||||||
|
compress: false
|
||||||
|
create_backup: true
|
||||||
|
|
||||||
|
# Logging settings
|
||||||
|
logging:
|
||||||
|
level: INFO
|
||||||
|
format: "%(asctime)s - %(levelname)s - %(message)s"
|
||||||
|
file: "processing.log"
|
||||||
39
test/skills/file-reference-skill/templates/report.md
Normal file
39
test/skills/file-reference-skill/templates/report.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Data Processing Report
|
||||||
|
|
||||||
|
**Generated**: {timestamp}
|
||||||
|
**Skill**: file-reference-skill
|
||||||
|
|
||||||
|
## Input Summary
|
||||||
|
|
||||||
|
- **Input File**: {input_file}
|
||||||
|
- **File Size**: {input_size} bytes
|
||||||
|
- **Format**: {format}
|
||||||
|
- **Encoding**: {encoding}
|
||||||
|
|
||||||
|
## Processing Summary
|
||||||
|
|
||||||
|
- **Start Time**: {start_time}
|
||||||
|
- **End Time**: {end_time}
|
||||||
|
- **Duration**: {duration} seconds
|
||||||
|
- **Status**: {status}
|
||||||
|
|
||||||
|
## Output Summary
|
||||||
|
|
||||||
|
- **Output File**: {output_file}
|
||||||
|
- **Output Size**: {output_size} bytes
|
||||||
|
- **Records Processed**: {record_count}
|
||||||
|
- **Errors**: {error_count}
|
||||||
|
|
||||||
|
## Validation Results
|
||||||
|
|
||||||
|
{validation_results}
|
||||||
|
|
||||||
|
## Processing Log
|
||||||
|
|
||||||
|
```
|
||||||
|
{processing_log}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This report was generated by the file-reference-skill example skill.*
|
||||||
37
test/skills/git-helper/SKILL.md
Normal file
37
test/skills/git-helper/SKILL.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
name: git-helper
|
||||||
|
description: Generate git commit messages and help with git workflows
|
||||||
|
allowed-tools: Bash
|
||||||
|
---
|
||||||
|
|
||||||
|
# Git Helper Skill
|
||||||
|
|
||||||
|
You are a git workflow assistant. Help users with commit messages, branch naming, and git best practices.
|
||||||
|
|
||||||
|
## Commit Message Format
|
||||||
|
|
||||||
|
Follow conventional commits specification:
|
||||||
|
- **feat**: New feature
|
||||||
|
- **fix**: Bug fix
|
||||||
|
- **docs**: Documentation changes
|
||||||
|
- **style**: Formatting, missing semicolons, etc.
|
||||||
|
- **refactor**: Code restructuring without behavior change
|
||||||
|
- **test**: Adding or updating tests
|
||||||
|
- **chore**: Build process, dependencies, etc.
|
||||||
|
|
||||||
|
Format:
|
||||||
|
```
|
||||||
|
<type>(<scope>): <subject>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
Provide a well-formatted commit message or git workflow guidance.
|
||||||
25
test/skills/markdown-formatter/SKILL.md
Normal file
25
test/skills/markdown-formatter/SKILL.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
name: markdown-formatter
|
||||||
|
description: Format and clean up markdown documents following best practices
|
||||||
|
---
|
||||||
|
|
||||||
|
# Markdown Formatter Skill
|
||||||
|
|
||||||
|
You are a markdown formatting expert. Clean up and standardize markdown documents.
|
||||||
|
|
||||||
|
## Formatting Rules
|
||||||
|
|
||||||
|
1. **Headers**: Ensure proper hierarchy (single H1, incremental levels)
|
||||||
|
2. **Lists**: Consistent bullet style (- for unordered, 1. for ordered)
|
||||||
|
3. **Code Blocks**: Proper language tags for syntax highlighting
|
||||||
|
4. **Links**: Convert inline links to reference-style when repeated
|
||||||
|
5. **Spacing**: Blank lines around headers, lists, and code blocks
|
||||||
|
6. **Line Length**: Wrap lines at 80-100 characters for readability
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
Provide the formatted markdown with a brief summary of changes made.
|
||||||
13
test/skills/nested-example/SKILL.md
Normal file
13
test/skills/nested-example/SKILL.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: nested-root-skill
|
||||||
|
description: Example skill at the root of the nested structure demonstrating depth 1
|
||||||
|
---
|
||||||
|
|
||||||
|
# Nested Root Skill
|
||||||
|
|
||||||
|
This skill demonstrates that skills can exist at any level of the hierarchy.
|
||||||
|
|
||||||
|
**Location**: `nested-example/SKILL.md`
|
||||||
|
**Depth**: 1 level from root (immediate subdirectory)
|
||||||
|
|
||||||
|
The nested structure supports flexible organization with skills at every level.
|
||||||
13
test/skills/nested-example/category-a/SKILL.md
Normal file
13
test/skills/nested-example/category-a/SKILL.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: nested-mid-skill
|
||||||
|
description: Example skill demonstrating nested directory structure at depth 2
|
||||||
|
---
|
||||||
|
|
||||||
|
# Nested Mid-Level Skill
|
||||||
|
|
||||||
|
This skill demonstrates mid-level nesting in skill organization.
|
||||||
|
|
||||||
|
**Location**: `category-a/SKILL.md`
|
||||||
|
**Depth**: 2 levels from root
|
||||||
|
|
||||||
|
Skills can be organized in categories and subcategories for better organization.
|
||||||
13
test/skills/nested-example/category-a/subcategory-1/SKILL.md
Normal file
13
test/skills/nested-example/category-a/subcategory-1/SKILL.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: nested-deep-skill
|
||||||
|
description: Example skill demonstrating nested directory structure at depth 3
|
||||||
|
---
|
||||||
|
|
||||||
|
# Nested Deep Skill
|
||||||
|
|
||||||
|
This skill demonstrates that skillkit can discover skills in nested subdirectories.
|
||||||
|
|
||||||
|
**Location**: `category-a/subcategory-1/SKILL.md`
|
||||||
|
**Depth**: 3 levels from root
|
||||||
|
|
||||||
|
You can organize your skills in any nested structure up to 5 levels deep.
|
||||||
13
test/skills/nested-example/category-b/SKILL.md
Normal file
13
test/skills/nested-example/category-b/SKILL.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: nested-category-b
|
||||||
|
description: Example skill in category B demonstrating flat organization within nested structure
|
||||||
|
---
|
||||||
|
|
||||||
|
# Nested Category B Skill
|
||||||
|
|
||||||
|
This skill demonstrates that you can mix flat and nested structures.
|
||||||
|
|
||||||
|
**Location**: `category-b/SKILL.md`
|
||||||
|
**Depth**: 2 levels from root
|
||||||
|
|
||||||
|
Some categories may have immediate skills, while others may have deeper nesting.
|
||||||
77
test/skills/pdf-extractor/SKILL.md
Normal file
77
test/skills/pdf-extractor/SKILL.md
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
name: pdf-extractor
|
||||||
|
description: Extract and convert PDF documents using Python scripts
|
||||||
|
version: 1.0.0
|
||||||
|
allowed-tools:
|
||||||
|
- Bash
|
||||||
|
- Read
|
||||||
|
- Write
|
||||||
|
---
|
||||||
|
|
||||||
|
# PDF Extractor Skill
|
||||||
|
|
||||||
|
This skill provides tools for extracting text and metadata from PDF documents and converting them to different formats.
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
### extract.py
|
||||||
|
Extracts text and metadata from PDF files.
|
||||||
|
|
||||||
|
**Input**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"file_path": "/path/to/document.pdf",
|
||||||
|
"pages": "all" | [1, 2, 3]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"text": "Extracted text content...",
|
||||||
|
"metadata": {
|
||||||
|
"title": "Document Title",
|
||||||
|
"author": "Author Name",
|
||||||
|
"pages": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### convert.sh
|
||||||
|
Converts PDF files to different formats (text, markdown, etc.).
|
||||||
|
|
||||||
|
**Input**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"input_file": "/path/to/input.pdf",
|
||||||
|
"output_format": "txt" | "md" | "html"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### parse.py
|
||||||
|
Parses structured data from PDF forms and tables.
|
||||||
|
|
||||||
|
**Input**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"file_path": "/path/to/form.pdf",
|
||||||
|
"extract_tables": true,
|
||||||
|
"extract_forms": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
from skillkit import SkillManager
|
||||||
|
|
||||||
|
manager = SkillManager()
|
||||||
|
result = manager.execute_skill_script(
|
||||||
|
skill_name="pdf-extractor",
|
||||||
|
script_name="extract",
|
||||||
|
arguments={"file_path": "document.pdf", "pages": "all"}
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.success:
|
||||||
|
print(result.stdout)
|
||||||
|
```
|
||||||
46
test/skills/pdf-extractor/scripts/convert.sh
Normal file
46
test/skills/pdf-extractor/scripts/convert.sh
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Convert PDF files to different formats
|
||||||
|
#
|
||||||
|
# This script demonstrates shell script support in skillkit.
|
||||||
|
# It reads JSON from stdin and performs format conversion.
|
||||||
|
#
|
||||||
|
# Environment variables available:
|
||||||
|
# - SKILL_NAME
|
||||||
|
# - SKILL_BASE_DIR
|
||||||
|
# - SKILL_VERSION
|
||||||
|
# - SKILLKIT_VERSION
|
||||||
|
|
||||||
|
# Read JSON input from stdin
|
||||||
|
read -r json_input
|
||||||
|
|
||||||
|
# Parse JSON using Python (for simplicity)
|
||||||
|
input_file=$(echo "$json_input" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('input_file', ''))")
|
||||||
|
output_format=$(echo "$json_input" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('output_format', 'txt'))")
|
||||||
|
|
||||||
|
# Validate input
|
||||||
|
if [ -z "$input_file" ]; then
|
||||||
|
echo '{"error": "Missing required argument: input_file"}' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mock conversion (in real implementation, would use tools like pdftotext, pandoc, etc.)
|
||||||
|
output_file="${input_file%.pdf}.${output_format}"
|
||||||
|
|
||||||
|
# Output result
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"input_file": "$input_file",
|
||||||
|
"output_file": "$output_file",
|
||||||
|
"output_format": "$output_format",
|
||||||
|
"message": "Converted $input_file to $output_format format",
|
||||||
|
"environment": {
|
||||||
|
"skill_name": "$SKILL_NAME",
|
||||||
|
"skill_base_dir": "$SKILL_BASE_DIR",
|
||||||
|
"skill_version": "$SKILL_VERSION",
|
||||||
|
"skillkit_version": "$SKILLKIT_VERSION"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
exit 0
|
||||||
97
test/skills/pdf-extractor/scripts/extract.py
Normal file
97
test/skills/pdf-extractor/scripts/extract.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Extract text and metadata from PDF files.
|
||||||
|
|
||||||
|
This script demonstrates reading JSON arguments from stdin,
|
||||||
|
processing them, and outputting results in JSON format.
|
||||||
|
|
||||||
|
Environment Variables:
|
||||||
|
- SKILL_NAME: Name of the parent skill
|
||||||
|
- SKILL_BASE_DIR: Base directory of the skill
|
||||||
|
- SKILL_VERSION: Version of the skill
|
||||||
|
- SKILLKIT_VERSION: Version of skillkit
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def extract_pdf(file_path: str, pages: str | list):
|
||||||
|
"""
|
||||||
|
Extract text from PDF file (mock implementation).
|
||||||
|
|
||||||
|
In a real implementation, this would use a library like PyPDF2 or pdfplumber.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: Path to the PDF file
|
||||||
|
pages: "all" or list of page numbers
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict with extracted text and metadata
|
||||||
|
"""
|
||||||
|
# Mock implementation for demonstration
|
||||||
|
return {
|
||||||
|
"text": f"Extracted text from {file_path}",
|
||||||
|
"metadata": {
|
||||||
|
"title": "Sample Document",
|
||||||
|
"author": "skillkit",
|
||||||
|
"pages": 10,
|
||||||
|
"file_path": file_path,
|
||||||
|
"requested_pages": pages
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"skill_name": os.getenv("SKILL_NAME"),
|
||||||
|
"skill_base_dir": os.getenv("SKILL_BASE_DIR"),
|
||||||
|
"skill_version": os.getenv("SKILL_VERSION"),
|
||||||
|
"skillkit_version": os.getenv("SKILLKIT_VERSION")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point for the PDF extraction script."""
|
||||||
|
try:
|
||||||
|
# Read JSON arguments from stdin
|
||||||
|
args = json.load(sys.stdin)
|
||||||
|
|
||||||
|
# Validate required arguments
|
||||||
|
if "file_path" not in args:
|
||||||
|
raise ValueError("Missing required argument: file_path")
|
||||||
|
|
||||||
|
# Extract optional arguments
|
||||||
|
file_path = args["file_path"]
|
||||||
|
pages = args.get("pages", "all")
|
||||||
|
|
||||||
|
# Perform extraction
|
||||||
|
result = extract_pdf(file_path, pages)
|
||||||
|
|
||||||
|
# Output result as JSON
|
||||||
|
print(json.dumps(result, indent=2))
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
error = {
|
||||||
|
"error": "Invalid JSON input",
|
||||||
|
"details": str(e)
|
||||||
|
}
|
||||||
|
print(json.dumps(error), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
error = {
|
||||||
|
"error": "Invalid arguments",
|
||||||
|
"details": str(e)
|
||||||
|
}
|
||||||
|
print(json.dumps(error), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error = {
|
||||||
|
"error": "Unexpected error",
|
||||||
|
"details": str(e)
|
||||||
|
}
|
||||||
|
print(json.dumps(error), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
94
test/skills/pdf-extractor/scripts/parse.py
Normal file
94
test/skills/pdf-extractor/scripts/parse.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Parse structured data from PDF forms and tables.
|
||||||
|
|
||||||
|
This script demonstrates advanced PDF processing capabilities.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def parse_pdf(file_path: str, extract_tables: bool, extract_forms: bool):
|
||||||
|
"""
|
||||||
|
Parse structured data from PDF (mock implementation).
|
||||||
|
|
||||||
|
In a real implementation, this would use libraries like:
|
||||||
|
- tabula-py or camelot for table extraction
|
||||||
|
- PyPDF2 or pdfplumber for form field extraction
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: Path to the PDF file
|
||||||
|
extract_tables: Whether to extract tables
|
||||||
|
extract_forms: Whether to extract form fields
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict with parsed data
|
||||||
|
"""
|
||||||
|
result = {
|
||||||
|
"file_path": file_path,
|
||||||
|
"extracted_data": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if extract_tables:
|
||||||
|
result["extracted_data"]["tables"] = [
|
||||||
|
{
|
||||||
|
"page": 1,
|
||||||
|
"rows": 5,
|
||||||
|
"columns": 3,
|
||||||
|
"data": [
|
||||||
|
["Header1", "Header2", "Header3"],
|
||||||
|
["Row1Col1", "Row1Col2", "Row1Col3"],
|
||||||
|
["Row2Col1", "Row2Col2", "Row2Col3"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if extract_forms:
|
||||||
|
result["extracted_data"]["forms"] = {
|
||||||
|
"name": "John Doe",
|
||||||
|
"email": "john@example.com",
|
||||||
|
"checkbox_agree": True
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point for PDF parsing script."""
|
||||||
|
try:
|
||||||
|
# Read JSON arguments from stdin
|
||||||
|
args = json.load(sys.stdin)
|
||||||
|
|
||||||
|
# Validate and extract arguments
|
||||||
|
file_path = args.get("file_path")
|
||||||
|
if not file_path:
|
||||||
|
raise ValueError("Missing required argument: file_path")
|
||||||
|
|
||||||
|
extract_tables = args.get("extract_tables", False)
|
||||||
|
extract_forms = args.get("extract_forms", False)
|
||||||
|
|
||||||
|
# Perform parsing
|
||||||
|
result = parse_pdf(file_path, extract_tables, extract_forms)
|
||||||
|
|
||||||
|
# Output result as JSON
|
||||||
|
print(json.dumps(result, indent=2))
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
error = {"error": "Invalid JSON input", "details": str(e)}
|
||||||
|
print(json.dumps(error), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
error = {"error": "Invalid arguments", "details": str(e)}
|
||||||
|
print(json.dumps(error), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error = {"error": "Unexpected error", "details": str(e)}
|
||||||
|
print(json.dumps(error), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
24
test/skills/pdf-tools/SKILL.md
Normal file
24
test/skills/pdf-tools/SKILL.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
name: pdf-tools
|
||||||
|
description: Extract and convert PDF documents using Python scripts
|
||||||
|
version: 1.0.0
|
||||||
|
allowed-tools:
|
||||||
|
- Bash
|
||||||
|
- Read
|
||||||
|
- Write
|
||||||
|
---
|
||||||
|
|
||||||
|
# PDF Tools Skill
|
||||||
|
|
||||||
|
This skill provides tools for extracting text and metadata from PDF documents and converting them to different formats.
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
### extract.py
|
||||||
|
see [extract](extract.md)
|
||||||
|
|
||||||
|
### convert.sh
|
||||||
|
see [convert](convert.md)
|
||||||
|
|
||||||
|
### parse.py
|
||||||
|
see [parse](parse.md)
|
||||||
11
test/skills/pdf-tools/convert.md
Normal file
11
test/skills/pdf-tools/convert.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# convert.md
|
||||||
|
Converts PDF files to different formats (text, markdown, etc.).
|
||||||
|
|
||||||
|
**Input**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"input_file": "/path/to/input.pdf",
|
||||||
|
"output_format": "txt" | "md" | "html"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
38
test/skills/pdf-tools/extract.md
Normal file
38
test/skills/pdf-tools/extract.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# extract.py
|
||||||
|
Extracts text and metadata from PDF files.
|
||||||
|
|
||||||
|
**Input**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"file_path": "/path/to/document.pdf",
|
||||||
|
"pages": "all" | [1, 2, 3]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"text": "Extracted text content...",
|
||||||
|
"metadata": {
|
||||||
|
"title": "Document Title",
|
||||||
|
"author": "Author Name",
|
||||||
|
"pages": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
from skillkit import SkillManager
|
||||||
|
|
||||||
|
manager = SkillManager()
|
||||||
|
result = manager.execute_skill_script(
|
||||||
|
skill_name="pdf-extractor",
|
||||||
|
script_name="extract",
|
||||||
|
arguments={"file_path": "document.pdf", "pages": "all"}
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.success:
|
||||||
|
print(result.stdout)
|
||||||
|
```
|
||||||
12
test/skills/pdf-tools/parse.md
Normal file
12
test/skills/pdf-tools/parse.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# parse.py
|
||||||
|
Parses structured data from PDF forms and tables.
|
||||||
|
|
||||||
|
**Input**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"file_path": "/path/to/form.pdf",
|
||||||
|
"extract_tables": true,
|
||||||
|
"extract_forms": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
46
test/skills/pdf-tools/scripts/convert.sh
Normal file
46
test/skills/pdf-tools/scripts/convert.sh
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Convert PDF files to different formats
|
||||||
|
#
|
||||||
|
# This script demonstrates shell script support in skillkit.
|
||||||
|
# It reads JSON from stdin and performs format conversion.
|
||||||
|
#
|
||||||
|
# Environment variables available:
|
||||||
|
# - SKILL_NAME
|
||||||
|
# - SKILL_BASE_DIR
|
||||||
|
# - SKILL_VERSION
|
||||||
|
# - SKILLKIT_VERSION
|
||||||
|
|
||||||
|
# Read JSON input from stdin
|
||||||
|
read -r json_input
|
||||||
|
|
||||||
|
# Parse JSON using Python (for simplicity)
|
||||||
|
input_file=$(echo "$json_input" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('input_file', ''))")
|
||||||
|
output_format=$(echo "$json_input" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('output_format', 'txt'))")
|
||||||
|
|
||||||
|
# Validate input
|
||||||
|
if [ -z "$input_file" ]; then
|
||||||
|
echo '{"error": "Missing required argument: input_file"}' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mock conversion (in real implementation, would use tools like pdftotext, pandoc, etc.)
|
||||||
|
output_file="${input_file%.pdf}.${output_format}"
|
||||||
|
|
||||||
|
# Output result
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"input_file": "$input_file",
|
||||||
|
"output_file": "$output_file",
|
||||||
|
"output_format": "$output_format",
|
||||||
|
"message": "Converted $input_file to $output_format format",
|
||||||
|
"environment": {
|
||||||
|
"skill_name": "$SKILL_NAME",
|
||||||
|
"skill_base_dir": "$SKILL_BASE_DIR",
|
||||||
|
"skill_version": "$SKILL_VERSION",
|
||||||
|
"skillkit_version": "$SKILLKIT_VERSION"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
exit 0
|
||||||
97
test/skills/pdf-tools/scripts/extract.py
Normal file
97
test/skills/pdf-tools/scripts/extract.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Extract text and metadata from PDF files.
|
||||||
|
|
||||||
|
This script demonstrates reading JSON arguments from stdin,
|
||||||
|
processing them, and outputting results in JSON format.
|
||||||
|
|
||||||
|
Environment Variables:
|
||||||
|
- SKILL_NAME: Name of the parent skill
|
||||||
|
- SKILL_BASE_DIR: Base directory of the skill
|
||||||
|
- SKILL_VERSION: Version of the skill
|
||||||
|
- SKILLKIT_VERSION: Version of skillkit
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def extract_pdf(file_path: str, pages: str | list):
|
||||||
|
"""
|
||||||
|
Extract text from PDF file (mock implementation).
|
||||||
|
|
||||||
|
In a real implementation, this would use a library like PyPDF2 or pdfplumber.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: Path to the PDF file
|
||||||
|
pages: "all" or list of page numbers
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict with extracted text and metadata
|
||||||
|
"""
|
||||||
|
# Mock implementation for demonstration
|
||||||
|
return {
|
||||||
|
"text": f"Extracted text from {file_path}",
|
||||||
|
"metadata": {
|
||||||
|
"title": "Sample Document",
|
||||||
|
"author": "skillkit",
|
||||||
|
"pages": 10,
|
||||||
|
"file_path": file_path,
|
||||||
|
"requested_pages": pages
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"skill_name": os.getenv("SKILL_NAME"),
|
||||||
|
"skill_base_dir": os.getenv("SKILL_BASE_DIR"),
|
||||||
|
"skill_version": os.getenv("SKILL_VERSION"),
|
||||||
|
"skillkit_version": os.getenv("SKILLKIT_VERSION")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point for the PDF extraction script."""
|
||||||
|
try:
|
||||||
|
# Read JSON arguments from stdin
|
||||||
|
args = json.load(sys.stdin)
|
||||||
|
|
||||||
|
# Validate required arguments
|
||||||
|
if "file_path" not in args:
|
||||||
|
raise ValueError("Missing required argument: file_path")
|
||||||
|
|
||||||
|
# Extract optional arguments
|
||||||
|
file_path = args["file_path"]
|
||||||
|
pages = args.get("pages", "all")
|
||||||
|
|
||||||
|
# Perform extraction
|
||||||
|
result = extract_pdf(file_path, pages)
|
||||||
|
|
||||||
|
# Output result as JSON
|
||||||
|
print(json.dumps(result, indent=2))
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
error = {
|
||||||
|
"error": "Invalid JSON input",
|
||||||
|
"details": str(e)
|
||||||
|
}
|
||||||
|
print(json.dumps(error), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
error = {
|
||||||
|
"error": "Invalid arguments",
|
||||||
|
"details": str(e)
|
||||||
|
}
|
||||||
|
print(json.dumps(error), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error = {
|
||||||
|
"error": "Unexpected error",
|
||||||
|
"details": str(e)
|
||||||
|
}
|
||||||
|
print(json.dumps(error), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
94
test/skills/pdf-tools/scripts/parse.py
Normal file
94
test/skills/pdf-tools/scripts/parse.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Parse structured data from PDF forms and tables.
|
||||||
|
|
||||||
|
This script demonstrates advanced PDF processing capabilities.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def parse_pdf(file_path: str, extract_tables: bool, extract_forms: bool):
|
||||||
|
"""
|
||||||
|
Parse structured data from PDF (mock implementation).
|
||||||
|
|
||||||
|
In a real implementation, this would use libraries like:
|
||||||
|
- tabula-py or camelot for table extraction
|
||||||
|
- PyPDF2 or pdfplumber for form field extraction
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: Path to the PDF file
|
||||||
|
extract_tables: Whether to extract tables
|
||||||
|
extract_forms: Whether to extract form fields
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict with parsed data
|
||||||
|
"""
|
||||||
|
result = {
|
||||||
|
"file_path": file_path,
|
||||||
|
"extracted_data": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if extract_tables:
|
||||||
|
result["extracted_data"]["tables"] = [
|
||||||
|
{
|
||||||
|
"page": 1,
|
||||||
|
"rows": 5,
|
||||||
|
"columns": 3,
|
||||||
|
"data": [
|
||||||
|
["Header1", "Header2", "Header3"],
|
||||||
|
["Row1Col1", "Row1Col2", "Row1Col3"],
|
||||||
|
["Row2Col1", "Row2Col2", "Row2Col3"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if extract_forms:
|
||||||
|
result["extracted_data"]["forms"] = {
|
||||||
|
"name": "John Doe",
|
||||||
|
"email": "john@example.com",
|
||||||
|
"checkbox_agree": True
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point for PDF parsing script."""
|
||||||
|
try:
|
||||||
|
# Read JSON arguments from stdin
|
||||||
|
args = json.load(sys.stdin)
|
||||||
|
|
||||||
|
# Validate and extract arguments
|
||||||
|
file_path = args.get("file_path")
|
||||||
|
if not file_path:
|
||||||
|
raise ValueError("Missing required argument: file_path")
|
||||||
|
|
||||||
|
extract_tables = args.get("extract_tables", False)
|
||||||
|
extract_forms = args.get("extract_forms", False)
|
||||||
|
|
||||||
|
# Perform parsing
|
||||||
|
result = parse_pdf(file_path, extract_tables, extract_forms)
|
||||||
|
|
||||||
|
# Output result as JSON
|
||||||
|
print(json.dumps(result, indent=2))
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
error = {"error": "Invalid JSON input", "details": str(e)}
|
||||||
|
print(json.dumps(error), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
error = {"error": "Invalid arguments", "details": str(e)}
|
||||||
|
print(json.dumps(error), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error = {"error": "Unexpected error", "details": str(e)}
|
||||||
|
print(json.dumps(error), file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
17
test/t.py
Normal file
17
test/t.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from skillagent.agent import skillagent
|
||||||
|
from ahserver.myTE import TemplateEngine
|
||||||
|
from ahserver.serverenv import ServerEnv
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
def setup_template():
|
||||||
|
engine = TemplateEngine()
|
||||||
|
g = ServerEnv()
|
||||||
|
g.tmpl_engine = engine
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
setup_template()
|
||||||
|
# await skillagent('55QMNxVgvlh8nyis0P1BE', 'eYgNuD6sVQgbj-khOOUNU', './skills')
|
||||||
|
await skillagent('09Xzu3kQ-K98Ewy2KtyU3', 'eYgNuD6sVQgbj-khOOUNU', './skills')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
Loading…
x
Reference in New Issue
Block a user