Jido.AI.Skill.Resources (Jido AI v2.2.0)

Copy Markdown View Source

Progressive disclosure of skill resources without eager loading.

Provides APIs to enumerate and lazily load resources bundled with skills:

  • scripts/ - Executable scripts and automation
  • references/ - Documentation and reference materials
  • assets/ - Binary assets (images, binaries, etc.)

All paths are resolved relative to the skill root directory, preventing path traversal attacks.

Usage

# List all resources in a skill
resources = Jido.AI.Skill.Resources.list_resources(skill_root)
# => %{scripts: [...], references: [...], assets: [...]}

# Load a specific resource
{:ok, content} = Jido.AI.Skill.Resources.load_resource(skill_root, "references/guide.md")

# Check resource exists without loading
true = Jido.AI.Skill.Resources.exists?(skill_root, "scripts/setup.sh")

Summary

Functions

Checks if a resource exists without loading it.

Lists resources of a specific type.

Lists all resources in a skill directory.

Loads a resource file lazily.

Loads a resource as UTF-8 text.

Resolves a relative path to an absolute path within the skill root.

Gets information about a specific resource without loading it.

Searches for resources matching a pattern.

Types

resource_info()

@type resource_info() :: %{
  name: String.t(),
  relative_path: String.t(),
  absolute_path: String.t(),
  size: non_neg_integer() | nil,
  modified: DateTime.t() | nil
}

resource_listing()

@type resource_listing() :: %{
  scripts: [resource_info()],
  references: [resource_info()],
  assets: [resource_info()]
}

resource_type()

@type resource_type() :: :scripts | :references | :assets

Functions

exists?(skill_root, relative_path)

@spec exists?(String.t(), String.t()) :: boolean()

Checks if a resource exists without loading it.

Examples

true = Jido.AI.Skill.Resources.exists?(skill_root, "scripts/setup.sh")
false = Jido.AI.Skill.Resources.exists?(skill_root, "missing.txt")

list_by_type(skill_root, type)

@spec list_by_type(String.t(), resource_type()) :: [resource_info()]

Lists resources of a specific type.

Examples

scripts = Jido.AI.Skill.Resources.list_by_type(skill_root, :scripts)

list_resources(skill_root)

@spec list_resources(String.t()) :: resource_listing()

Lists all resources in a skill directory.

Returns a map with resources grouped by type, without loading content.

Examples

resources = Jido.AI.Skill.Resources.list_resources("/path/to/skill")
IO.inspect(resources.scripts)     # [%{name: "deploy.sh", ...}]
IO.inspect(resources.references)    # [%{name: "api.md", ...}]
IO.inspect(resources.assets)        # [%{name: "logo.png", ...}]

load_resource(skill_root, relative_path)

@spec load_resource(String.t(), String.t()) :: {:ok, binary()} | {:error, atom()}

Loads a resource file lazily.

Returns the file content only when requested. Validates the path is within the skill root to prevent directory traversal.

Returns

  • {:ok, binary} - Resource loaded successfully
  • {:error, :not_found} - Resource doesn't exist
  • {:error, :path_traversal} - Attempted path traversal attack

Examples

{:ok, content} = Jido.AI.Skill.Resources.load_resource(skill_root, "references/api.md")
{:error, :not_found} = Jido.AI.Skill.Resources.load_resource(skill_root, "missing.txt")

# Path traversal is blocked:
{:error, :path_traversal} = Jido.AI.Skill.Resources.load_resource(skill_root, "../../../etc/passwd")

load_resource_text(skill_root, relative_path)

@spec load_resource_text(String.t(), String.t()) ::
  {:ok, String.t()} | {:error, atom()}

Loads a resource as UTF-8 text.

Same as load_resource/2 but validates the content is valid UTF-8.

Returns

  • {:ok, String.t()} - Resource loaded and valid UTF-8
  • {:error, :invalid_utf8} - Content is not valid UTF-8
  • {:error, reason} - Other errors from load_resource/2

resolve_path(skill_root, relative_path)

@spec resolve_path(String.t(), String.t()) ::
  {:ok, String.t()} | {:error, :path_traversal}

Resolves a relative path to an absolute path within the skill root.

Validates the resolved path is within the skill root directory to prevent path traversal attacks.

Returns

  • {:ok, absolute_path} - Path resolved successfully
  • {:error, :path_traversal} - Path escapes skill root

Examples

{:ok, "/skill/references/guide.md"} = Jido.AI.Skill.Resources.resolve_path("/skill", "references/guide.md")
{:error, :path_traversal} = Jido.AI.Skill.Resources.resolve_path("/skill", "../../../etc/passwd")

resource_info(skill_root, relative_path)

@spec resource_info(String.t(), String.t()) ::
  {:ok, resource_info()} | {:error, :not_found}

Gets information about a specific resource without loading it.

Returns

  • {:ok, resource_info} - Resource exists, info returned
  • {:error, :not_found} - Resource doesn't exist

Examples

{:ok, info} = Jido.AI.Skill.Resources.resource_info(skill_root, "references/api.md")
IO.puts("Size: #{info.size}")

search(skill_root, pattern)

@spec search(String.t(), String.t()) :: [resource_info()]

Searches for resources matching a pattern.

Examples

# Find all markdown files in references
matches = Jido.AI.Skill.Resources.search(skill_root, "references/**/*.md")