yardx 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- yard/__init__.py +0 -0
- yard/console/console_ui.py +14 -0
- yard/console/error.py +12 -0
- yard/console/help_oargs.py +58 -0
- yard/helper/__init__.py +0 -0
- yard/helper/depfile.py +66 -0
- yard/intent_agent.py +79 -0
- yard/logger_config.py +16 -0
- yard/prompt/agent_prompt.py +3 -0
- yard/prompt/intent_prompt.py +77 -0
- yard/prompt/tasks/dockerfile_prompt.py +21 -0
- yard/prompt/tasks/examples/dockerfile_example.py +161 -0
- yard/schema/intent_schema.py +87 -0
- yard/schema/yard_schema.py +32 -0
- yard/tools/__init__.py +0 -0
- yard/tools/create_dockerfile.py +40 -0
- yard/tools/docker_ignore_file.py +26 -0
- yard/yard_agent.py +89 -0
- yard/yard_cli.py +103 -0
- yardx-0.1.2.dist-info/METADATA +64 -0
- yardx-0.1.2.dist-info/RECORD +25 -0
- yardx-0.1.2.dist-info/WHEEL +5 -0
- yardx-0.1.2.dist-info/entry_points.txt +2 -0
- yardx-0.1.2.dist-info/licenses/LICENSE +21 -0
- yardx-0.1.2.dist-info/top_level.txt +1 -0
yard/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from rich.console import Console
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
console = Console()
|
|
6
|
+
|
|
7
|
+
def success(message: str) -> None:
|
|
8
|
+
console.print(f"[green]✓{message}[/green]")
|
|
9
|
+
|
|
10
|
+
def info(message: str) -> None:
|
|
11
|
+
console.print(f"[cyan]{message}[/cyan]")
|
|
12
|
+
|
|
13
|
+
def error(message: str) -> None:
|
|
14
|
+
console.print(f"[red]{message}[/red]")
|
yard/console/error.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from rich import print as rprint
|
|
2
|
+
from rich.panel import Panel
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def command_error(cmd):
|
|
6
|
+
rprint("[yellow]Usage:[/yellow] yard [PROMPT]")
|
|
7
|
+
rprint(Panel(renderable=f"No such '{cmd}' command",
|
|
8
|
+
title="Error",
|
|
9
|
+
title_align="left",
|
|
10
|
+
border_style="red"
|
|
11
|
+
))
|
|
12
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from rich.panel import Panel
|
|
2
|
+
from rich import print as rprint
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def custom_help():
|
|
6
|
+
rprint("[bold yellow]Usage:[/bold yellow] [cyan]yard[/cyan] [white]<prompt>[/white]\n")
|
|
7
|
+
|
|
8
|
+
rprint(
|
|
9
|
+
Panel(
|
|
10
|
+
renderable=(
|
|
11
|
+
"[bold cyan]PROMPT[/bold cyan]\n"
|
|
12
|
+
"Describe the task you want the agent to perform.\n\n"
|
|
13
|
+
"[dim]The agent analyzes your request and generates the required project files automatically.[/dim]"
|
|
14
|
+
),
|
|
15
|
+
title="Arguments",
|
|
16
|
+
title_align="left",
|
|
17
|
+
border_style="dim"
|
|
18
|
+
)
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
rprint(
|
|
22
|
+
Panel(
|
|
23
|
+
renderable=(
|
|
24
|
+
"[bold cyan]--help[/bold cyan] [white]Show this message and exit[/white]\n" \
|
|
25
|
+
"[bold cyan]-h[/bold cyan] [white]Show this message and exit[/white]"
|
|
26
|
+
),
|
|
27
|
+
title="Options",
|
|
28
|
+
title_align="left",
|
|
29
|
+
border_style="dim"
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
rprint(
|
|
34
|
+
Panel(
|
|
35
|
+
renderable=(
|
|
36
|
+
"[white]Supported Tasks[/white]\n\n"
|
|
37
|
+
"• Create Dockerfile\n"
|
|
38
|
+
"• Create .dockerignore\n"
|
|
39
|
+
),
|
|
40
|
+
title="Capabilities",
|
|
41
|
+
title_align="left",
|
|
42
|
+
border_style="yellow"
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
rprint(
|
|
47
|
+
Panel(
|
|
48
|
+
renderable=(
|
|
49
|
+
'[bold yellow]yard[/bold yellow] '
|
|
50
|
+
'[cyan]"Create a Dockerfile for main.py"[/cyan]\n\n'
|
|
51
|
+
'[bold yellow]yard[/bold yellow] '
|
|
52
|
+
'[cyan]"Create a .dockerignore file and add the files and folders that should be ignored"[/cyan]'
|
|
53
|
+
),
|
|
54
|
+
title="Examples",
|
|
55
|
+
title_align="left",
|
|
56
|
+
border_style="white"
|
|
57
|
+
)
|
|
58
|
+
)
|
yard/helper/__init__.py
ADDED
|
File without changes
|
yard/helper/depfile.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from yard.console.console_ui import console, success
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def find_dep_file(start=None):
|
|
7
|
+
if start is None:
|
|
8
|
+
current_root = Path(__file__).resolve()
|
|
9
|
+
else:
|
|
10
|
+
current_root = Path(start).resolve()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
project_root = [current_root] + list(current_root.parents)
|
|
14
|
+
|
|
15
|
+
for parent in project_root:
|
|
16
|
+
|
|
17
|
+
with console.status(
|
|
18
|
+
"[cyan]Resolving dependencies[cyan]"
|
|
19
|
+
):
|
|
20
|
+
# Search for pyproject.toml file in the root directory
|
|
21
|
+
if (parent / "pyproject.toml").exists():
|
|
22
|
+
|
|
23
|
+
success("Dependencies resolved")
|
|
24
|
+
|
|
25
|
+
with Path.open(parent/"pyproject.toml") as f:
|
|
26
|
+
config = f.read()
|
|
27
|
+
|
|
28
|
+
with console.status(
|
|
29
|
+
"[cyan]Detecting Python environment[/cyan]"
|
|
30
|
+
):
|
|
31
|
+
match = re.search(r'requires-python\s*=\s*"((?:<=|>=|==|<|>|=)\s*([0-9.]+))"', config)
|
|
32
|
+
if match:
|
|
33
|
+
version = match.group(1)
|
|
34
|
+
|
|
35
|
+
success(f"Detected Python {version}")
|
|
36
|
+
|
|
37
|
+
return "pyproject.toml", version
|
|
38
|
+
|
|
39
|
+
# Search for requirements.txt file in the root directory
|
|
40
|
+
elif (parent / "requirements.txt").exists():
|
|
41
|
+
|
|
42
|
+
success("✓ Dependencies resolved")
|
|
43
|
+
|
|
44
|
+
with console.status(
|
|
45
|
+
"[cyan]Detecting Python environment[/cyan]"
|
|
46
|
+
):
|
|
47
|
+
for venv_name in [".venv", "venv", "env"]:
|
|
48
|
+
# Searching python version in the venv
|
|
49
|
+
cfg = parent / venv_name / "pyvenv.cfg"
|
|
50
|
+
if cfg.exists():
|
|
51
|
+
text = cfg.read_text()
|
|
52
|
+
|
|
53
|
+
match = re.search(r"version\s*=\s*([0-9.]+)", text)
|
|
54
|
+
if match:
|
|
55
|
+
version = match.group(1)
|
|
56
|
+
|
|
57
|
+
success(f"Detected Python {version}")
|
|
58
|
+
|
|
59
|
+
return "requirements.txt", version
|
|
60
|
+
|
|
61
|
+
return None, None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
print(find_dep_file())
|
yard/intent_agent.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
from dotenv import load_dotenv
|
|
4
|
+
import json
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from openai import AsyncOpenAI
|
|
8
|
+
|
|
9
|
+
from yard.prompt.intent_prompt import SYSTEM_PROMPT
|
|
10
|
+
from yard.schema.intent_schema import INTENT_TOOLS
|
|
11
|
+
|
|
12
|
+
from yard.logger_config import get_logger
|
|
13
|
+
|
|
14
|
+
logger = get_logger(__name__)
|
|
15
|
+
|
|
16
|
+
load_dotenv()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_openai_client():
|
|
20
|
+
api_key = os.environ.get("OPENAI_API_KEY")
|
|
21
|
+
|
|
22
|
+
if not api_key:
|
|
23
|
+
raise RuntimeError("OPENAI_API_KEY is not set")
|
|
24
|
+
|
|
25
|
+
return AsyncOpenAI(
|
|
26
|
+
api_key=api_key,
|
|
27
|
+
timeout=60
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def intent_identifier(intent):
|
|
32
|
+
|
|
33
|
+
client = get_openai_client()
|
|
34
|
+
|
|
35
|
+
messages = [{
|
|
36
|
+
"role": "system",
|
|
37
|
+
"content": SYSTEM_PROMPT
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"role": "user",
|
|
41
|
+
"content": intent
|
|
42
|
+
}]
|
|
43
|
+
|
|
44
|
+
response = await client.chat.completions.create(
|
|
45
|
+
model="gpt-4.1-nano",
|
|
46
|
+
messages=messages,
|
|
47
|
+
tools=INTENT_TOOLS,
|
|
48
|
+
tool_choice="auto"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
messages = response.choices[0].message
|
|
52
|
+
|
|
53
|
+
if messages.tool_calls:
|
|
54
|
+
tool_call = messages.tool_calls[0]
|
|
55
|
+
|
|
56
|
+
task_name = tool_call.function.name
|
|
57
|
+
|
|
58
|
+
arguments = json.loads(
|
|
59
|
+
tool_call.function.arguments
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if task_name == "non_docker_request":
|
|
64
|
+
logger.info("Task is not related to Docker")
|
|
65
|
+
return {
|
|
66
|
+
"success" : False,
|
|
67
|
+
"task" : task_name,
|
|
68
|
+
"data" : arguments
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
logger.info(f"Task {task_name} Indentified")
|
|
72
|
+
return {
|
|
73
|
+
"success": True,
|
|
74
|
+
"task": task_name,
|
|
75
|
+
"data": arguments
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
print(asyncio.run(intent_identifier("Create a dockerfile for modelai.py from src directory")))
|
yard/logger_config.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
logging.basicConfig(
|
|
5
|
+
level=logging.INFO,
|
|
6
|
+
format="%(asctime)s | %(name)s | %(levelname)s | %(message)s",
|
|
7
|
+
handlers=[
|
|
8
|
+
logging.FileHandler("app.log"),
|
|
9
|
+
]
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_logger(name):
|
|
14
|
+
return logging.getLogger(name)
|
|
15
|
+
|
|
16
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
SYSTEM_PROMPT = """
|
|
2
|
+
You are an intent extracting engine. Your job is to convert the user's message into structured JSON.
|
|
3
|
+
|
|
4
|
+
Rules:
|
|
5
|
+
- Understand the user's message correctly.
|
|
6
|
+
- Identify the filename with the .py suffix in the user's message.
|
|
7
|
+
- STRICTLY user intent must be related to Docker ONLY.
|
|
8
|
+
- If the user request is related to Docker or DevOps:
|
|
9
|
+
- Return results ONLY through `tool_calls`.
|
|
10
|
+
- `message.content` MUST remain completely empty.
|
|
11
|
+
- Strictly follow the provided tool schema.
|
|
12
|
+
- Do not change the schema structure.
|
|
13
|
+
- Do not rename fields.
|
|
14
|
+
- If the user request is NOT related to Docker, containers, DevOps, deployment, infrastructure, CI/CD, orchestration, or Docker ecosystem tasks:
|
|
15
|
+
- Return the `non_docker_request` tool
|
|
16
|
+
- Do NOT write any Docker-related files (such as "Dockerfile", "compose.yaml", or "dockerignore") as values for the "file" key in the JSON schema.
|
|
17
|
+
|
|
18
|
+
DOCKERIGNORE RULES:
|
|
19
|
+
- STRICTLY do NOT use `\n`, escaped newline characters, or unnecessary backslashes in `.dockerignore`.
|
|
20
|
+
- Write entries as clean plain lines only.
|
|
21
|
+
- Do not serialize file contents using escaped strings.
|
|
22
|
+
- Preserve raw formatting for all generated files.
|
|
23
|
+
|
|
24
|
+
#Examples
|
|
25
|
+
|
|
26
|
+
##Example - 1:
|
|
27
|
+
INPUT:
|
|
28
|
+
Create a two dockerfiles: one for main.py and another for engine.py in src folder"
|
|
29
|
+
|
|
30
|
+
OUTPUT:
|
|
31
|
+
{
|
|
32
|
+
"tasks" : "create_dockerfile",
|
|
33
|
+
"runnable" : [{
|
|
34
|
+
"file" : "main.py",
|
|
35
|
+
"folder" : null
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"file" : "engine.py",
|
|
39
|
+
"folder" : "src"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
##Example - 2:
|
|
45
|
+
INPUT:
|
|
46
|
+
Create a .dockerignore file and add all necessary Python project files and folders that should be ignored, including the src/engine.py file and src/tests folder.
|
|
47
|
+
|
|
48
|
+
OUTPUT:
|
|
49
|
+
{ "task" : "create_dockerignore",
|
|
50
|
+
"ignored" : [
|
|
51
|
+
".env",
|
|
52
|
+
".env_local",
|
|
53
|
+
".python_version",
|
|
54
|
+
"venv/",
|
|
55
|
+
".venv/",
|
|
56
|
+
"__pycache__/",
|
|
57
|
+
".mypy_cache",
|
|
58
|
+
".ruff_cache",
|
|
59
|
+
".git",
|
|
60
|
+
"build",
|
|
61
|
+
"src/engine.py",
|
|
62
|
+
"src/tests"
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
##Example - 3:
|
|
67
|
+
INPUT:
|
|
68
|
+
Create a python file and write hello world script
|
|
69
|
+
|
|
70
|
+
OUTPUT:
|
|
71
|
+
{
|
|
72
|
+
"task" : "non_docker_request",
|
|
73
|
+
"message" : I am a specialized Docker DevOps Engineer. Provide tasks related to Docker.
|
|
74
|
+
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
"""
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from yard.prompt.tasks.examples.dockerfile_example import EXAMPLE_TEMPLATES
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def prompt(intent, dep_file, version):
|
|
6
|
+
return f"""Generate a Dockerfile based on the following project intent: `{intent}`.
|
|
7
|
+
Use '{dep_file}' as the dependency file.
|
|
8
|
+
Use Python version '{version}'.
|
|
9
|
+
|
|
10
|
+
RULES:
|
|
11
|
+
- Generate ONLY valid Dockerfile content and Docker-related instructions that strictly follow the structure and format of the provided example template.
|
|
12
|
+
- Do NOT include explanations, markdown, notes, headings, or additional text outside the required Docker output.
|
|
13
|
+
- If generating more that one Dockerfile, name each file using the format `Dockerfile.<filename>`.
|
|
14
|
+
- If the dependency file is `requirements.txt`, use `pip` as the package manager.
|
|
15
|
+
- If the dependency file is `pyproject.toml`, use `uv sync` to install and synchronize dependencies.
|
|
16
|
+
- Do not assume or invent the Python version. Use only the Python version explicitly provided by the user.
|
|
17
|
+
|
|
18
|
+
EXAMPLES:
|
|
19
|
+
`{EXAMPLE_TEMPLATES}'
|
|
20
|
+
|
|
21
|
+
"""
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
EXAMPLE_TEMPLATES = """
|
|
2
|
+
#Example-1:
|
|
3
|
+
INPUT:
|
|
4
|
+
Generate a Dockerfile based on the following project intent:
|
|
5
|
+
`{
|
|
6
|
+
"task":"create_dockerfile",
|
|
7
|
+
"runnable":[
|
|
8
|
+
{
|
|
9
|
+
"file":"modelai.py",
|
|
10
|
+
"folder":"src"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"file": "main.py",
|
|
14
|
+
"folder" : null
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}`.
|
|
18
|
+
Use 'pyproject.toml' as the dependency file.
|
|
19
|
+
Use Python version '=> 3.13'.
|
|
20
|
+
|
|
21
|
+
Output:
|
|
22
|
+
`{'dockerfiles' : [{
|
|
23
|
+
'dockerfile_name' : 'Dockerfile.modelai',
|
|
24
|
+
'dockerfile_instructions' : '
|
|
25
|
+
FROM python:3.13-slim
|
|
26
|
+
|
|
27
|
+
# Prevents Python from creating .pyc files
|
|
28
|
+
# Ensures logs are shown immediately in Docker
|
|
29
|
+
# Makes uv use the system Python inside the container
|
|
30
|
+
PYTHONDONETWRITEBYTECODE=1 \
|
|
31
|
+
PYTHONUNBUFFERED=1 \
|
|
32
|
+
UV_SYSTEM_PYTHON=1
|
|
33
|
+
|
|
34
|
+
# Set working directory
|
|
35
|
+
WORKDIR /app
|
|
36
|
+
|
|
37
|
+
# Install uv package manager
|
|
38
|
+
RUN pip install -no-cache-dir uv
|
|
39
|
+
|
|
40
|
+
# Copy dependency files
|
|
41
|
+
COPY pyproject.toml uv.lock ./
|
|
42
|
+
|
|
43
|
+
# Install project dependencies
|
|
44
|
+
RUN uv sync --frozen --no-dev --no-cache
|
|
45
|
+
|
|
46
|
+
# Copy application source code
|
|
47
|
+
COPY src/ ./src/
|
|
48
|
+
|
|
49
|
+
# Start the application
|
|
50
|
+
CMD ["uv", "run", "-m", "src.modelai"] '
|
|
51
|
+
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
'dockerfile_name' : 'Dockerfile.main',
|
|
55
|
+
'dockerfile_instructions' : '
|
|
56
|
+
FROM python:3.13-slim
|
|
57
|
+
|
|
58
|
+
# Prevents Python from creating .pyc files
|
|
59
|
+
# Ensures logs are shown immediately in Docker
|
|
60
|
+
# Makes uv use the system Python inside the container
|
|
61
|
+
PYTHONDONETWRITEBYTECODE=1 \
|
|
62
|
+
PYTHONUNBUFFERED=1 \
|
|
63
|
+
UV_SYSTEM_PYTHON=1
|
|
64
|
+
|
|
65
|
+
# Set working directory
|
|
66
|
+
WORKDIR /app
|
|
67
|
+
|
|
68
|
+
# Install uv package manager
|
|
69
|
+
RUN pip install -no-cache-dir uv
|
|
70
|
+
|
|
71
|
+
# Copy dependency files
|
|
72
|
+
COPY pyproject.toml uv.lock* ./
|
|
73
|
+
|
|
74
|
+
# Install project dependencies
|
|
75
|
+
RUN uv sync --frozen --no-dev --no-cache
|
|
76
|
+
|
|
77
|
+
# Copy application source code
|
|
78
|
+
COPY . .
|
|
79
|
+
|
|
80
|
+
# Start the application
|
|
81
|
+
CMD ["uv", "run", "main.py"] '
|
|
82
|
+
|
|
83
|
+
}] }`
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
##Example - 2:
|
|
87
|
+
INPUT:
|
|
88
|
+
Generate a Dockerfile based on the following project intent:
|
|
89
|
+
`{
|
|
90
|
+
"task":"create_dockerfile",
|
|
91
|
+
"runnable":[
|
|
92
|
+
{
|
|
93
|
+
"file":"main.py",
|
|
94
|
+
"folder": null
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"file": "app.py",
|
|
98
|
+
"folder" : app
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}`.
|
|
102
|
+
Use 'requirements.txt' as the dependency file.
|
|
103
|
+
Use Python version '=> 3.13'.
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
OUTPUT:
|
|
107
|
+
`{ 'dockerfiles': [
|
|
108
|
+
{
|
|
109
|
+
'dockerfile_name' : 'Dockerfile.main',
|
|
110
|
+
'docker_instructions': '
|
|
111
|
+
FROM python:3.13-slim
|
|
112
|
+
|
|
113
|
+
# Prevents Python from creating .pyc files
|
|
114
|
+
# Ensures logs are shown immediately in Docker
|
|
115
|
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
|
116
|
+
PYTHONUNBUFFERED=1
|
|
117
|
+
|
|
118
|
+
# Set working directory
|
|
119
|
+
WORKDIR / app
|
|
120
|
+
|
|
121
|
+
# Copy project files
|
|
122
|
+
COPY requirements.txt .
|
|
123
|
+
|
|
124
|
+
# Install dependencies
|
|
125
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
126
|
+
|
|
127
|
+
# Copy application source code
|
|
128
|
+
COPY . .
|
|
129
|
+
|
|
130
|
+
# Run main.py
|
|
131
|
+
CMD ["python", "main.py"] '
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
'dockerfile_name' : 'Dockerfile.app',
|
|
135
|
+
'docker_instructions': '
|
|
136
|
+
FROM python:3.13-slim
|
|
137
|
+
|
|
138
|
+
# Prevents Python from creating .pyc files
|
|
139
|
+
# Ensures logs are shown immediately in Docker
|
|
140
|
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
|
141
|
+
PYTHONUNBUFFERED=1
|
|
142
|
+
|
|
143
|
+
# Set working directory
|
|
144
|
+
WORKDIR /app
|
|
145
|
+
|
|
146
|
+
# Copy dependency file
|
|
147
|
+
COPY requirements.txt .
|
|
148
|
+
|
|
149
|
+
# Install project dependencies
|
|
150
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
151
|
+
|
|
152
|
+
# Copy application source code
|
|
153
|
+
COPY app/ ./app/
|
|
154
|
+
|
|
155
|
+
# Start the application
|
|
156
|
+
CMD["python", "-m", "app.app"]
|
|
157
|
+
|
|
158
|
+
}]}`
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
"""
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
INTENT_TOOLS = [
|
|
2
|
+
{
|
|
3
|
+
"type" : "function",
|
|
4
|
+
"function" : {
|
|
5
|
+
"name" : "create_dockerfile",
|
|
6
|
+
"description": "Extract creating dockerfile tasks with files and folders",
|
|
7
|
+
"strict" : True,
|
|
8
|
+
"parameters" : {
|
|
9
|
+
"type" : "object",
|
|
10
|
+
"properties" : {
|
|
11
|
+
"task" : {
|
|
12
|
+
"type" : "string",
|
|
13
|
+
"enum" : ["create_dockerfile"]
|
|
14
|
+
},
|
|
15
|
+
"runnable" : {
|
|
16
|
+
"type" : "array",
|
|
17
|
+
"items" : {
|
|
18
|
+
"type" : "object",
|
|
19
|
+
"properties" : {
|
|
20
|
+
"file" : {
|
|
21
|
+
"type" : ["string", "null"]
|
|
22
|
+
},
|
|
23
|
+
"folder" : {
|
|
24
|
+
"type" : ["string", "null"]
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"required" : ["file", "folder"],
|
|
28
|
+
"additionalProperties" : False
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"required" : [
|
|
33
|
+
"task",
|
|
34
|
+
"runnable"
|
|
35
|
+
],
|
|
36
|
+
"additionalProperties" : False
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"type" : "function",
|
|
42
|
+
"function": {
|
|
43
|
+
"name" : "create_dockerignore",
|
|
44
|
+
"description" : "Extract files and folders to ignore and Create a .dockerignore file",
|
|
45
|
+
"strict" : True,
|
|
46
|
+
"parameters" : {
|
|
47
|
+
"type" : "object",
|
|
48
|
+
"properties" : {
|
|
49
|
+
"task" : {
|
|
50
|
+
"type" : "string",
|
|
51
|
+
"enum" : ["create_dockerignore"]
|
|
52
|
+
},
|
|
53
|
+
"ignored": {
|
|
54
|
+
"type" : "array",
|
|
55
|
+
"items" : {
|
|
56
|
+
"type" : "string"
|
|
57
|
+
},
|
|
58
|
+
"description" : "ingored files and folders"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"required" : ["task","ignored"],
|
|
62
|
+
"additionalProperties" : False
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"type": "function",
|
|
68
|
+
"function": {
|
|
69
|
+
"name": "non_docker_request",
|
|
70
|
+
"description": "Use this when the request is not related to Docker or DevOps",
|
|
71
|
+
"strict": True,
|
|
72
|
+
"parameters": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"properties": {
|
|
75
|
+
"message": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"enum": [
|
|
78
|
+
"I am a specialized Docker DevOps Engineer. Provide tasks related to Docker."
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"required": ["message"],
|
|
83
|
+
"additionalProperties": False
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
TOOLS = [
|
|
2
|
+
{
|
|
3
|
+
"type" : "function",
|
|
4
|
+
"function" : {
|
|
5
|
+
"name" : "create_dockerfile",
|
|
6
|
+
"description" : "Create a Dockerfile using given information",
|
|
7
|
+
"parameters" : {
|
|
8
|
+
"type" : "object",
|
|
9
|
+
"properties" : {
|
|
10
|
+
"dockerfiles" : {
|
|
11
|
+
"type" : "array",
|
|
12
|
+
"items" : {
|
|
13
|
+
"type" : "object",
|
|
14
|
+
"properties" : {
|
|
15
|
+
"dockerfile_name" : {
|
|
16
|
+
"type" : "string"
|
|
17
|
+
},
|
|
18
|
+
"dockerfile_instructions" : {
|
|
19
|
+
"type" : "string"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"required" : ["dockerfile_name", "dockerfile_instructions"],
|
|
23
|
+
"additionalProperties" : False
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"required" : ["dockerfiles"],
|
|
28
|
+
"additionalProperties" : False
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
]
|
yard/tools/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
from yard.logger_config import get_logger
|
|
4
|
+
from yard.yard_agent import yard_devops
|
|
5
|
+
from yard.helper.depfile import find_dep_file
|
|
6
|
+
from yard.console.console_ui import console, success
|
|
7
|
+
from yard.prompt.tasks.dockerfile_prompt import prompt
|
|
8
|
+
|
|
9
|
+
logger = get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def create_dockerfile(intent):
|
|
13
|
+
|
|
14
|
+
dep_filename, version = find_dep_file()
|
|
15
|
+
|
|
16
|
+
prompts = prompt(intent, dep_filename, version)
|
|
17
|
+
|
|
18
|
+
with console.status(
|
|
19
|
+
"[cyan]Generating Dockerfile[/cyan]"
|
|
20
|
+
):
|
|
21
|
+
logger.info("Dispatching request to yard agent")
|
|
22
|
+
|
|
23
|
+
output = await yard_devops(prompts)
|
|
24
|
+
|
|
25
|
+
logger.info(json.dumps(output, indent=2))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
for doc in output["dockerfiles"]:
|
|
29
|
+
|
|
30
|
+
logger.info(f"creating {doc["dockerfile_name"]} and writing instructions")
|
|
31
|
+
success(f"Writing Docker instructions for {doc["dockerfile_name"]}")
|
|
32
|
+
|
|
33
|
+
with open(doc["dockerfile_name"], "w") as file:
|
|
34
|
+
file.write(doc["dockerfile_instructions"])
|
|
35
|
+
|
|
36
|
+
success(f"Wrote {doc['dockerfile_name']}")
|
|
37
|
+
logger.info(f"{doc["dockerfile_name"]} created successfully")
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
asyncio.run(create_dockerfile("""{"intent":"create_dockerfile","runnable_file":[{"file":"modelai.py","folder":"src"}],"mode":"development","response":null}"""))
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from yard.console.console_ui import success
|
|
4
|
+
from yard.logger_config import get_logger
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
logger = get_logger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def dockerignore_file(intent):
|
|
13
|
+
|
|
14
|
+
logger.info(json.dumps(intent, indent=2))
|
|
15
|
+
success(f"Adding ignored files and folders")
|
|
16
|
+
|
|
17
|
+
files = intent["ignored"]
|
|
18
|
+
|
|
19
|
+
logger.info("Adding ignored files and folders")
|
|
20
|
+
success(f"Adding ignored files and folders")
|
|
21
|
+
for file in files:
|
|
22
|
+
with open(".dockerignore", "a+") as f:
|
|
23
|
+
f.write(f"{file}\n")
|
|
24
|
+
|
|
25
|
+
success(".dockerignore file created")
|
|
26
|
+
logger.info("dockerignore file created")
|
yard/yard_agent.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
from logging import WARNING
|
|
5
|
+
from dotenv import load_dotenv
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from tenacity import (
|
|
9
|
+
retry,
|
|
10
|
+
stop_after_attempt,
|
|
11
|
+
wait_random_exponential,
|
|
12
|
+
retry_if_exception_type,
|
|
13
|
+
before_sleep_log
|
|
14
|
+
)
|
|
15
|
+
from openai import AsyncOpenAI
|
|
16
|
+
from openai import (
|
|
17
|
+
APIConnectionError,
|
|
18
|
+
APITimeoutError,
|
|
19
|
+
RateLimitError,
|
|
20
|
+
InternalServerError,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from yard.logger_config import get_logger
|
|
24
|
+
from yard.prompt.agent_prompt import SYSTEM_PROMPT
|
|
25
|
+
from yard.schema.yard_schema import TOOLS
|
|
26
|
+
from yard.console.console_ui import error
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
logger = get_logger(__name__)
|
|
31
|
+
load_dotenv()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@retry(
|
|
36
|
+
retry=retry_if_exception_type((
|
|
37
|
+
APIConnectionError,
|
|
38
|
+
APITimeoutError,
|
|
39
|
+
RateLimitError,
|
|
40
|
+
InternalServerError,
|
|
41
|
+
)),
|
|
42
|
+
wait=wait_random_exponential(
|
|
43
|
+
multiplier=1,
|
|
44
|
+
max=60,
|
|
45
|
+
),
|
|
46
|
+
stop=stop_after_attempt(8),
|
|
47
|
+
before_sleep=before_sleep_log(
|
|
48
|
+
logger,
|
|
49
|
+
WARNING
|
|
50
|
+
),
|
|
51
|
+
reraise=True
|
|
52
|
+
)
|
|
53
|
+
async def yard_devops(intent):
|
|
54
|
+
|
|
55
|
+
client = AsyncOpenAI(timeout=60)
|
|
56
|
+
|
|
57
|
+
messages = [{
|
|
58
|
+
"role": "system",
|
|
59
|
+
"content": SYSTEM_PROMPT
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"role": "user",
|
|
63
|
+
"content": intent
|
|
64
|
+
}]
|
|
65
|
+
|
|
66
|
+
logger.info("Send OpenAI request")
|
|
67
|
+
response = await client.chat.completions.create(
|
|
68
|
+
model="gpt-5.4",
|
|
69
|
+
messages=messages,
|
|
70
|
+
tools=TOOLS,
|
|
71
|
+
tool_choice="auto"
|
|
72
|
+
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
logger.info("OpenAI response received")
|
|
76
|
+
message = response.choices[0].message
|
|
77
|
+
|
|
78
|
+
if message.tool_calls:
|
|
79
|
+
content = message.tool_calls[0]
|
|
80
|
+
|
|
81
|
+
output = json.loads(
|
|
82
|
+
content.function.arguments
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
logger.info("Tool call parsed successfully")
|
|
86
|
+
return output
|
|
87
|
+
|
|
88
|
+
return None
|
|
89
|
+
|
yard/yard_cli.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import asyncio
|
|
3
|
+
import typer
|
|
4
|
+
from typer import Typer, Option, Argument
|
|
5
|
+
from typer.core import TyperGroup
|
|
6
|
+
from yard.intent_agent import intent_identifier
|
|
7
|
+
|
|
8
|
+
from yard.console.help_oargs import custom_help
|
|
9
|
+
from yard.console.console_ui import console, info, success
|
|
10
|
+
from yard.console.error import command_error
|
|
11
|
+
from yard.logger_config import get_logger
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from yard.tools.create_dockerfile import create_dockerfile
|
|
16
|
+
from yard.tools.docker_ignore_file import dockerignore_file
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
VERSION = "0.1.2"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CustomGroup(TyperGroup):
|
|
25
|
+
def get_command(self, ctx, cmd_name):
|
|
26
|
+
rv = super().get_command(ctx, cmd_name)
|
|
27
|
+
|
|
28
|
+
if rv is not None:
|
|
29
|
+
return rv
|
|
30
|
+
|
|
31
|
+
command_error(cmd_name)
|
|
32
|
+
|
|
33
|
+
raise typer.Exit(code=1)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
app = Typer(cls=CustomGroup,
|
|
37
|
+
# Typer stops decorating exceptions
|
|
38
|
+
pretty_exceptions_enable=False,
|
|
39
|
+
context_settings={
|
|
40
|
+
# To prevent using the typer's default behaviour of "--help" or '-h'
|
|
41
|
+
"help_option_names" : []
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@app.callback(invoke_without_command=True)
|
|
46
|
+
def yard_agent(
|
|
47
|
+
prompt: str = Argument(""),
|
|
48
|
+
help: bool = Option(
|
|
49
|
+
False,
|
|
50
|
+
"--help",
|
|
51
|
+
"-h",
|
|
52
|
+
is_eager=True,
|
|
53
|
+
),
|
|
54
|
+
version: bool = Option(
|
|
55
|
+
False,
|
|
56
|
+
"--version",
|
|
57
|
+
"-v",
|
|
58
|
+
is_eager=True,
|
|
59
|
+
),
|
|
60
|
+
):
|
|
61
|
+
with console.status(
|
|
62
|
+
"[cyan]Analyzing request[/cyan]"
|
|
63
|
+
):
|
|
64
|
+
if help:
|
|
65
|
+
custom_help()
|
|
66
|
+
raise typer.Exit()
|
|
67
|
+
|
|
68
|
+
if len(sys.argv) < 2:
|
|
69
|
+
custom_help()
|
|
70
|
+
raise typer.Exit()
|
|
71
|
+
|
|
72
|
+
if version:
|
|
73
|
+
print(VERSION)
|
|
74
|
+
raise typer.Exit()
|
|
75
|
+
try:
|
|
76
|
+
logger.info("Sending the user message to intent agent")
|
|
77
|
+
|
|
78
|
+
result = asyncio.run(intent_identifier(prompt))
|
|
79
|
+
|
|
80
|
+
except RuntimeError as e:
|
|
81
|
+
print("OPENAI_API_KET is not set")
|
|
82
|
+
raise typer.Exit(code=1)
|
|
83
|
+
|
|
84
|
+
if result["success"] is True:
|
|
85
|
+
|
|
86
|
+
intent = result["data"]
|
|
87
|
+
|
|
88
|
+
if result["data"]["task"] == "create_dockerfile":
|
|
89
|
+
|
|
90
|
+
success(f"Prepared {result['data']['task']} tasks")
|
|
91
|
+
logger.info("Sending the dockerfile intent to create_dockerfile function")
|
|
92
|
+
|
|
93
|
+
asyncio.run(create_dockerfile(intent))
|
|
94
|
+
|
|
95
|
+
elif result["data"]["task"] == "create_dockerignore":
|
|
96
|
+
|
|
97
|
+
success(f"Prepared {result['data']['task']} tasks")
|
|
98
|
+
logger.info("Sending the ignored files to dockerignore_file function")
|
|
99
|
+
|
|
100
|
+
asyncio.run(dockerignore_file(intent))
|
|
101
|
+
else:
|
|
102
|
+
logger.info(f"{result["data"]["message"]}")
|
|
103
|
+
info(result["data"]["message"])
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yardx
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Terminal based AI agent for Docker
|
|
5
|
+
Requires-Python: >=3.13
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: openai>=2.33.0
|
|
9
|
+
Requires-Dist: python-dotenv>=1.2.2
|
|
10
|
+
Requires-Dist: tenacity>=9.1.4
|
|
11
|
+
Requires-Dist: typer>=0.25.1
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# Yard for Docker
|
|
15
|
+
|
|
16
|
+
Yard is a terminal-based AI agent for Docker. In this first version, it helps developers quickly generate Dockerfiles for Python projects directly from the terminal.
|
|
17
|
+
|
|
18
|
+
## Status
|
|
19
|
+
|
|
20
|
+
v1 — under active development.
|
|
21
|
+
|
|
22
|
+
## What it does
|
|
23
|
+
|
|
24
|
+
- Generates one or more Dockerfiles and a `.dockerignore` file from a simple command
|
|
25
|
+
- Detects the Python version from project metadata
|
|
26
|
+
- Helps package Python applications for Docker quickly from the terminal
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pipx install yard
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Setup
|
|
35
|
+
|
|
36
|
+
Yard currently uses OpenAI models.
|
|
37
|
+
|
|
38
|
+
### Windows PowerShell
|
|
39
|
+
|
|
40
|
+
```powershell
|
|
41
|
+
$env:OPENAI_API_KEY="your_api_key"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Linux/macOS
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
export OPENAI_API_KEY="your_api_key"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Example
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
yard "create a dockerfile for this main.py file"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Privacy
|
|
57
|
+
|
|
58
|
+
- This project does not send raw files, scripts, or `.env` variables to the AI
|
|
59
|
+
- No data is used for training AI models
|
|
60
|
+
- Yard only reads `pyproject.toml` or `pyvenv.cfg` files to detect the Python version
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
yard/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
yard/intent_agent.py,sha256=nHo4dWpt3udV-RytygcI_exBg2BZtFqFBoV91XLsq4g,1903
|
|
3
|
+
yard/logger_config.py,sha256=OLwSDimCbTuHJTdmwuXdC7MLQiN7TkCutJk01Hq43d0,269
|
|
4
|
+
yard/yard_agent.py,sha256=MmGj-hzj_CXZOo_GQ-n_JD_9WN57J164j5deK9E4ZGM,1901
|
|
5
|
+
yard/yard_cli.py,sha256=q6XnMFIJJMSQBN6zFYNEvnefxYd_9UNtRxP0oALHhbw,2772
|
|
6
|
+
yard/console/console_ui.py,sha256=k2sjKwV5vB0bRdhgPh4R5Dca-eGuggCq8RjOyyI0t-k,309
|
|
7
|
+
yard/console/error.py,sha256=8BcXFEvsEFVRp0zY1t8UBBYRcnwuyUEAvRbFTN1J-Mo,336
|
|
8
|
+
yard/console/help_oargs.py,sha256=3qETBRqek5_djPFayG30D1qpzKQSu8xgQFy8j1OJim4,1805
|
|
9
|
+
yard/helper/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
yard/helper/depfile.py,sha256=ob6b0WvlJtFho1nkJyaQTJyA_Y2-AauaHaALzjVD3z4,2257
|
|
11
|
+
yard/prompt/agent_prompt.py,sha256=uOw1QcdDuclTBwPgZTNUHpgiRpoUL1LcAdZn1gyinaY,162
|
|
12
|
+
yard/prompt/intent_prompt.py,sha256=UPRT4x4yxdV4nfbXAr5LmPZE5jkTrNvdSOv2FnQ1p0I,2343
|
|
13
|
+
yard/prompt/tasks/dockerfile_prompt.py,sha256=sxB2jy1KCbJlCIFuSWrCZUeC2f8Q-TNzFFnaZBcTue0,1008
|
|
14
|
+
yard/prompt/tasks/examples/dockerfile_example.py,sha256=1-TnJHh4nA4BKMfcg_zGlaKm0iSwauo9glrxRFCi3aA,7001
|
|
15
|
+
yard/schema/intent_schema.py,sha256=31ckh6N5eCn_HK5379bx9TXPhEpAVWFnnYFgo3SjsTY,2861
|
|
16
|
+
yard/schema/yard_schema.py,sha256=nr4148mz0aYXjGSrvYcOhYTSj7mO0sfYnWbzCblYe8Q,1184
|
|
17
|
+
yard/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
yard/tools/create_dockerfile.py,sha256=8H38K9wUy_djWcwYMbbnhMf448W_eIvBKt9XLas4ZNs,1376
|
|
19
|
+
yard/tools/docker_ignore_file.py,sha256=ObeNKsg3utH1ohvXsMD57FnNhctHIVHVk69bFupQKpQ,635
|
|
20
|
+
yardx-0.1.2.dist-info/licenses/LICENSE,sha256=x4Uaun97gEUWc_XpgEt98cwb4h6OTuAaZoo2-aqAFeE,1093
|
|
21
|
+
yardx-0.1.2.dist-info/METADATA,sha256=Yk-3zU7LxvhrAdx4k40j_dYTUyaAtonUnMZuC08P_dA,1393
|
|
22
|
+
yardx-0.1.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
23
|
+
yardx-0.1.2.dist-info/entry_points.txt,sha256=6pYNx-t6Tsni9NdSX_M_B0esiDK2aMLvsWO7njl62AM,43
|
|
24
|
+
yardx-0.1.2.dist-info/top_level.txt,sha256=GWIEw9vsBE5H6Ztm6mlO6JWemS-HcmFLvvqIs5LfPeE,5
|
|
25
|
+
yardx-0.1.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arul Dhanasekar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
yard
|