Add source field support and Fabric import script
- Add ToolSource dataclass for attribution metadata (type, license, url, author, original_tool) - Add source and version fields to Tool dataclass - Update Tool.from_dict() and to_dict() to handle source field - Display source attribution in TUI info panel - Show [imported]/[forked] markers in cmdforge list - Add import_fabric.py script to import Fabric patterns as CmdForge tools 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
@@ -23,7 +23,14 @@ def cmd_list(args):
|
||||
for name in tools:
|
||||
tool = load_tool(name)
|
||||
if tool:
|
||||
print(f" {name}")
|
||||
# Show source indicator for imported tools
|
||||
source_marker = ""
|
||||
if tool.source and tool.source.type == "imported":
|
||||
source_marker = " [imported]"
|
||||
elif tool.source and tool.source.type == "forked":
|
||||
source_marker = " [forked]"
|
||||
|
||||
print(f" {name}{source_marker}")
|
||||
print(f" {tool.description or 'No description'}")
|
||||
|
||||
# Show arguments
|
||||
|
||||
+47
-1
@@ -134,6 +134,38 @@ class ToolStep:
|
||||
Step = PromptStep | CodeStep | ToolStep
|
||||
|
||||
|
||||
@dataclass
|
||||
class ToolSource:
|
||||
"""Attribution and source information for imported/external tools."""
|
||||
type: str = "original" # "original", "imported", "forked"
|
||||
license: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
author: Optional[str] = None
|
||||
original_tool: Optional[str] = None # e.g., "fabric/patterns/extract_wisdom"
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
d = {"type": self.type}
|
||||
if self.license:
|
||||
d["license"] = self.license
|
||||
if self.url:
|
||||
d["url"] = self.url
|
||||
if self.author:
|
||||
d["author"] = self.author
|
||||
if self.original_tool:
|
||||
d["original_tool"] = self.original_tool
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "ToolSource":
|
||||
return cls(
|
||||
type=data.get("type", "original"),
|
||||
license=data.get("license"),
|
||||
url=data.get("url"),
|
||||
author=data.get("author"),
|
||||
original_tool=data.get("original_tool"),
|
||||
)
|
||||
|
||||
|
||||
# Default categories for organizing tools
|
||||
DEFAULT_CATEGORIES = ["Text", "Developer", "Data", "Other"]
|
||||
|
||||
@@ -148,6 +180,8 @@ class Tool:
|
||||
steps: List[Step] = field(default_factory=list)
|
||||
output: str = "{input}" # Output template
|
||||
dependencies: List[str] = field(default_factory=list) # Required tools for meta-tools
|
||||
source: Optional[ToolSource] = None # Attribution for imported/external tools
|
||||
version: str = "" # Tool version
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "Tool":
|
||||
@@ -164,6 +198,11 @@ class Tool:
|
||||
elif step.get("type") == "tool":
|
||||
steps.append(ToolStep.from_dict(step))
|
||||
|
||||
# Parse source attribution if present
|
||||
source = None
|
||||
if "source" in data:
|
||||
source = ToolSource.from_dict(data["source"])
|
||||
|
||||
return cls(
|
||||
name=data["name"],
|
||||
description=data.get("description", ""),
|
||||
@@ -171,7 +210,9 @@ class Tool:
|
||||
arguments=arguments,
|
||||
steps=steps,
|
||||
output=data.get("output", "{input}"),
|
||||
dependencies=data.get("dependencies", [])
|
||||
dependencies=data.get("dependencies", []),
|
||||
source=source,
|
||||
version=data.get("version", ""),
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
@@ -179,9 +220,14 @@ class Tool:
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
}
|
||||
if self.version:
|
||||
d["version"] = self.version
|
||||
# Only include category if it's not the default
|
||||
if self.category and self.category != "Other":
|
||||
d["category"] = self.category
|
||||
# Include source attribution if present
|
||||
if self.source:
|
||||
d["source"] = self.source.to_dict()
|
||||
if self.dependencies:
|
||||
d["dependencies"] = self.dependencies
|
||||
d["arguments"] = [arg.to_dict() for arg in self.arguments]
|
||||
|
||||
@@ -197,6 +197,7 @@ class CmdForgeUI:
|
||||
self._info_args = urwid.Text("")
|
||||
self._info_steps = urwid.Text("")
|
||||
self._info_output = urwid.Text("")
|
||||
self._info_source = urwid.Text("")
|
||||
|
||||
info_content = urwid.Pile([
|
||||
self._info_name,
|
||||
@@ -207,6 +208,8 @@ class CmdForgeUI:
|
||||
self._info_steps,
|
||||
urwid.Divider(),
|
||||
self._info_output,
|
||||
urwid.Divider(),
|
||||
self._info_source,
|
||||
])
|
||||
info_filler = urwid.Filler(info_content, valign='top')
|
||||
info_box = urwid.LineBox(info_filler, title='Tool Info')
|
||||
@@ -279,12 +282,29 @@ class CmdForgeUI:
|
||||
self._info_steps.set_text(steps_text.rstrip())
|
||||
|
||||
self._info_output.set_text(f"Output: {tool.output}")
|
||||
|
||||
# Display source attribution if present
|
||||
if tool.source:
|
||||
source_text = "Source:\n"
|
||||
source_text += f" Type: {tool.source.type}\n"
|
||||
if tool.source.author:
|
||||
source_text += f" Author: {tool.source.author}\n"
|
||||
if tool.source.license:
|
||||
source_text += f" License: {tool.source.license}\n"
|
||||
if tool.source.url:
|
||||
source_text += f" URL: {tool.source.url}\n"
|
||||
if tool.source.original_tool:
|
||||
source_text += f" Original: {tool.source.original_tool}\n"
|
||||
self._info_source.set_text(source_text.rstrip())
|
||||
else:
|
||||
self._info_source.set_text("")
|
||||
else:
|
||||
self._info_name.set_text("")
|
||||
self._info_desc.set_text("")
|
||||
self._info_args.set_text("")
|
||||
self._info_steps.set_text("")
|
||||
self._info_output.set_text("")
|
||||
self._info_source.set_text("")
|
||||
|
||||
def _on_tool_select(self, name):
|
||||
"""Called when a tool is selected (Enter/double-click)."""
|
||||
|
||||
Reference in New Issue
Block a user