textfsm-ai 0.1.19__tar.gz
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.
- textfsm_ai-0.1.19/LICENSE +21 -0
- textfsm_ai-0.1.19/MANIFEST.in +3 -0
- textfsm_ai-0.1.19/PKG-INFO +65 -0
- textfsm_ai-0.1.19/README.md +17 -0
- textfsm_ai-0.1.19/pyproject.toml +124 -0
- textfsm_ai-0.1.19/setup.cfg +4 -0
- textfsm_ai-0.1.19/textfsm_ai/__init__.py +5 -0
- textfsm_ai-0.1.19/textfsm_ai/ai_router.py +57 -0
- textfsm_ai-0.1.19/textfsm_ai/api.py +5 -0
- textfsm_ai-0.1.19/textfsm_ai/cli/__init__.py +1 -0
- textfsm_ai-0.1.19/textfsm_ai/cli/_utils.py +61 -0
- textfsm_ai-0.1.19/textfsm_ai/cli/generate_cmd.py +29 -0
- textfsm_ai-0.1.19/textfsm_ai/cli/providers_cmd.py +11 -0
- textfsm_ai-0.1.19/textfsm_ai/cli/quota_cmd.py +7 -0
- textfsm_ai-0.1.19/textfsm_ai/cli/top.py +35 -0
- textfsm_ai-0.1.19/textfsm_ai/cli/version_cmd.py +8 -0
- textfsm_ai-0.1.19/textfsm_ai/config.yaml +24 -0
- textfsm_ai-0.1.19/textfsm_ai/config_loader.py +9 -0
- textfsm_ai-0.1.19/textfsm_ai/providers/__init__.py +0 -0
- textfsm_ai-0.1.19/textfsm_ai/providers/claude_provider.py +32 -0
- textfsm_ai-0.1.19/textfsm_ai/providers/deepseek_provider.py +28 -0
- textfsm_ai-0.1.19/textfsm_ai/providers/gemini_provider.py +25 -0
- textfsm_ai-0.1.19/textfsm_ai/providers/openai_provider.py +28 -0
- textfsm_ai-0.1.19/textfsm_ai/quota_manager.py +84 -0
- textfsm_ai-0.1.19/textfsm_ai.egg-info/PKG-INFO +65 -0
- textfsm_ai-0.1.19/textfsm_ai.egg-info/SOURCES.txt +28 -0
- textfsm_ai-0.1.19/textfsm_ai.egg-info/dependency_links.txt +1 -0
- textfsm_ai-0.1.19/textfsm_ai.egg-info/entry_points.txt +2 -0
- textfsm_ai-0.1.19/textfsm_ai.egg-info/requires.txt +21 -0
- textfsm_ai-0.1.19/textfsm_ai.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Geeks Trident LLC
|
|
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,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: textfsm-ai
|
|
3
|
+
Version: 0.1.19
|
|
4
|
+
Summary: AI-powered TextFSM template generator with multi-provider routing
|
|
5
|
+
Author-email: Tuyen Mathew Duong <tuyen@geekstrident.com>
|
|
6
|
+
Maintainer-email: Tuyen Mathew Duong <tuyen@geekstrident.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/Geeks-Trident-LLC/textfsm-ai
|
|
9
|
+
Project-URL: Repository, https://github.com/Geeks-Trident-LLC/textfsm-ai
|
|
10
|
+
Project-URL: Issues, https://github.com/Geeks-Trident-LLC/textfsm-ai/issues
|
|
11
|
+
Project-URL: Documentation, https://geeks-trident-llc.github.io/textfsm-ai/latest/
|
|
12
|
+
Keywords: textfsm,network automation,cli parsing,template generator,ai,llm,openai,claude,gemini,deepseek,router,quota manager
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Information Technology
|
|
16
|
+
Classifier: Intended Audience :: System Administrators
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
23
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
24
|
+
Classifier: Topic :: Text Processing
|
|
25
|
+
Classifier: Topic :: Utilities
|
|
26
|
+
Classifier: Operating System :: OS Independent
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Requires-Dist: PyYAML>=6.0
|
|
31
|
+
Requires-Dist: requests>=2.31
|
|
32
|
+
Requires-Dist: click>=8.1
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: build; extra == "dev"
|
|
35
|
+
Requires-Dist: twine; extra == "dev"
|
|
36
|
+
Provides-Extra: test
|
|
37
|
+
Requires-Dist: pytest; extra == "test"
|
|
38
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
39
|
+
Provides-Extra: lint
|
|
40
|
+
Requires-Dist: ruff; extra == "lint"
|
|
41
|
+
Provides-Extra: all
|
|
42
|
+
Requires-Dist: build; extra == "all"
|
|
43
|
+
Requires-Dist: twine; extra == "all"
|
|
44
|
+
Requires-Dist: pytest; extra == "all"
|
|
45
|
+
Requires-Dist: pytest-cov; extra == "all"
|
|
46
|
+
Requires-Dist: ruff; extra == "all"
|
|
47
|
+
Dynamic: license-file
|
|
48
|
+
|
|
49
|
+
# textfsm-ai
|
|
50
|
+
|
|
51
|
+
**AI‑powered TextFSM template generation, parsing assistance, and smart log extraction.**
|
|
52
|
+
|
|
53
|
+
`textfsm-ai` brings modern LLM intelligence to traditional TextFSM workflows.
|
|
54
|
+
It helps you automatically generate templates, validate patterns, explain parsing logic, and accelerate network automation development.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 🚀 Features
|
|
59
|
+
|
|
60
|
+
- **AI‑Powered Template Generation** — Turn raw CLI output into production‑ready TextFSM templates in seconds.
|
|
61
|
+
- **Smart Validation & Refinement** — Automatically verify template correctness and refine ambiguous patterns with AI assistance.
|
|
62
|
+
- **Built‑In Golden Master Testing** — Lock in deterministic parsing behavior and detect regressions instantly.
|
|
63
|
+
- **Flexible Multi‑Provider AI Routing** — Use the best AI model for each task with automatic routing across supported cloud providers.
|
|
64
|
+
|
|
65
|
+
---
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# textfsm-ai
|
|
2
|
+
|
|
3
|
+
**AI‑powered TextFSM template generation, parsing assistance, and smart log extraction.**
|
|
4
|
+
|
|
5
|
+
`textfsm-ai` brings modern LLM intelligence to traditional TextFSM workflows.
|
|
6
|
+
It helps you automatically generate templates, validate patterns, explain parsing logic, and accelerate network automation development.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 🚀 Features
|
|
11
|
+
|
|
12
|
+
- **AI‑Powered Template Generation** — Turn raw CLI output into production‑ready TextFSM templates in seconds.
|
|
13
|
+
- **Smart Validation & Refinement** — Automatically verify template correctness and refine ambiguous patterns with AI assistance.
|
|
14
|
+
- **Built‑In Golden Master Testing** — Lock in deterministic parsing behavior and detect regressions instantly.
|
|
15
|
+
- **Flexible Multi‑Provider AI Routing** — Use the best AI model for each task with automatic routing across supported cloud providers.
|
|
16
|
+
|
|
17
|
+
---
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "textfsm-ai"
|
|
7
|
+
version = "0.1.19"
|
|
8
|
+
description = "AI-powered TextFSM template generator with multi-provider routing"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Tuyen Mathew Duong", email = "tuyen@geekstrident.com" }
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
maintainers = [
|
|
17
|
+
{ name = "Tuyen Mathew Duong", email = "tuyen@geekstrident.com" }
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
requires-python = ">=3.9"
|
|
21
|
+
|
|
22
|
+
dependencies = [
|
|
23
|
+
"PyYAML>=6.0",
|
|
24
|
+
"requests>=2.31",
|
|
25
|
+
"click>=8.1",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
classifiers = [
|
|
29
|
+
"Development Status :: 4 - Beta",
|
|
30
|
+
"Intended Audience :: Developers",
|
|
31
|
+
"Intended Audience :: Information Technology",
|
|
32
|
+
"Intended Audience :: System Administrators",
|
|
33
|
+
|
|
34
|
+
"Programming Language :: Python :: 3",
|
|
35
|
+
"Programming Language :: Python :: 3.9",
|
|
36
|
+
"Programming Language :: Python :: 3.10",
|
|
37
|
+
"Programming Language :: Python :: 3.11",
|
|
38
|
+
"Programming Language :: Python :: 3.12",
|
|
39
|
+
|
|
40
|
+
"Topic :: Software Development :: Libraries",
|
|
41
|
+
"Topic :: Software Development :: Code Generators",
|
|
42
|
+
"Topic :: Text Processing",
|
|
43
|
+
"Topic :: Utilities",
|
|
44
|
+
|
|
45
|
+
"Operating System :: OS Independent",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
keywords = [
|
|
49
|
+
"textfsm",
|
|
50
|
+
"network automation",
|
|
51
|
+
"cli parsing",
|
|
52
|
+
"template generator",
|
|
53
|
+
"ai",
|
|
54
|
+
"llm",
|
|
55
|
+
"openai",
|
|
56
|
+
"claude",
|
|
57
|
+
"gemini",
|
|
58
|
+
"deepseek",
|
|
59
|
+
"router",
|
|
60
|
+
"quota manager",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[project.optional-dependencies]
|
|
64
|
+
dev = [
|
|
65
|
+
"build",
|
|
66
|
+
"twine",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
test = [
|
|
70
|
+
"pytest",
|
|
71
|
+
"pytest-cov",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
lint = [
|
|
75
|
+
"ruff",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
all = [
|
|
79
|
+
"build",
|
|
80
|
+
"twine",
|
|
81
|
+
"pytest",
|
|
82
|
+
"pytest-cov",
|
|
83
|
+
"ruff",
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
[project.scripts]
|
|
87
|
+
textfsm-ai = "textfsm_ai.cli.top:main"
|
|
88
|
+
|
|
89
|
+
[project.urls]
|
|
90
|
+
Homepage = "https://github.com/Geeks-Trident-LLC/textfsm-ai"
|
|
91
|
+
Repository = "https://github.com/Geeks-Trident-LLC/textfsm-ai"
|
|
92
|
+
Issues = "https://github.com/Geeks-Trident-LLC/textfsm-ai/issues"
|
|
93
|
+
Documentation = "https://geeks-trident-llc.github.io/textfsm-ai/latest/"
|
|
94
|
+
|
|
95
|
+
[tool.setuptools]
|
|
96
|
+
include-package-data = true
|
|
97
|
+
|
|
98
|
+
[tool.setuptools.packages.find]
|
|
99
|
+
where = ["."]
|
|
100
|
+
include = ["textfsm_ai*"]
|
|
101
|
+
exclude = ["tests*", "docs*", "examples*"]
|
|
102
|
+
|
|
103
|
+
[tool.pytest.ini_options]
|
|
104
|
+
addopts = "-q --disable-warnings --maxfail=1"
|
|
105
|
+
|
|
106
|
+
# ---------------------------
|
|
107
|
+
# Formatting + Linting Tools
|
|
108
|
+
# ---------------------------
|
|
109
|
+
|
|
110
|
+
[tool.black]
|
|
111
|
+
line-length = 88
|
|
112
|
+
target-version = ["py39"]
|
|
113
|
+
|
|
114
|
+
[tool.isort]
|
|
115
|
+
profile = "black"
|
|
116
|
+
line_length = 88
|
|
117
|
+
|
|
118
|
+
[tool.ruff]
|
|
119
|
+
line-length = 88
|
|
120
|
+
target-version = "py39"
|
|
121
|
+
|
|
122
|
+
[tool.ruff.lint]
|
|
123
|
+
select = ["E", "F", "W"]
|
|
124
|
+
extend-select = ["I"]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from textfsm_ai.config_loader import load_config
|
|
2
|
+
from textfsm_ai.providers.claude_provider import ClaudeProvider
|
|
3
|
+
from textfsm_ai.providers.deepseek_provider import DeepSeekProvider
|
|
4
|
+
from textfsm_ai.providers.gemini_provider import GeminiProvider
|
|
5
|
+
from textfsm_ai.providers.openai_provider import OpenAIProvider
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MultiProviderRouter:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
cfg = load_config()
|
|
11
|
+
|
|
12
|
+
p = cfg["providers"]
|
|
13
|
+
|
|
14
|
+
self.openai = OpenAIProvider(
|
|
15
|
+
p["openai"]["api_key"],
|
|
16
|
+
p["openai"]["model"],
|
|
17
|
+
p["openai"]["daily_limit"],
|
|
18
|
+
p["openai"]["monthly_limit"],
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
self.claude = ClaudeProvider(
|
|
22
|
+
p["claude"]["api_key"],
|
|
23
|
+
p["claude"]["model"],
|
|
24
|
+
p["claude"]["daily_limit"],
|
|
25
|
+
p["claude"]["monthly_limit"],
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
self.gemini = GeminiProvider(
|
|
29
|
+
p["gemini"]["api_key"],
|
|
30
|
+
p["gemini"]["model"],
|
|
31
|
+
p["gemini"]["daily_limit"],
|
|
32
|
+
p["gemini"]["monthly_limit"],
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
self.deepseek = DeepSeekProvider(
|
|
36
|
+
p["deepseek"]["api_key"],
|
|
37
|
+
p["deepseek"]["model"],
|
|
38
|
+
p["deepseek"]["daily_limit"],
|
|
39
|
+
p["deepseek"]["monthly_limit"],
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
self.providers = [
|
|
43
|
+
self.gemini,
|
|
44
|
+
self.deepseek,
|
|
45
|
+
self.openai,
|
|
46
|
+
self.claude,
|
|
47
|
+
self.local,
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
def generate(self, prompt: str) -> str:
|
|
51
|
+
for provider in self.providers:
|
|
52
|
+
try:
|
|
53
|
+
return provider.generate(prompt)
|
|
54
|
+
except Exception:
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
raise RuntimeError("All providers failed")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI package for textfsm-ai."""
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import functools
|
|
3
|
+
import io
|
|
4
|
+
import json
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run_with_capture(ctx, func, *args, **kwargs):
|
|
11
|
+
"""Execute a subcommand, capture stdout, and wrap in JSON if requested.
|
|
12
|
+
|
|
13
|
+
Behavior:
|
|
14
|
+
- Default: print human-readable output.
|
|
15
|
+
- --time: append human-readable timing.
|
|
16
|
+
- --json: wrap stdout + elapsed_sec in JSON (always include elapsed_sec).
|
|
17
|
+
"""
|
|
18
|
+
use_json = ctx.obj.get("json", False)
|
|
19
|
+
use_time = ctx.obj.get("time", False)
|
|
20
|
+
|
|
21
|
+
buffer = io.StringIO()
|
|
22
|
+
|
|
23
|
+
start = time.perf_counter()
|
|
24
|
+
with contextlib.redirect_stdout(buffer):
|
|
25
|
+
func(*args, **kwargs)
|
|
26
|
+
elapsed = time.perf_counter() - start
|
|
27
|
+
|
|
28
|
+
output = buffer.getvalue()
|
|
29
|
+
|
|
30
|
+
if use_json:
|
|
31
|
+
payload = {
|
|
32
|
+
"output": output,
|
|
33
|
+
"elapsed_sec": round(elapsed, 6),
|
|
34
|
+
}
|
|
35
|
+
click.echo(json.dumps(payload, indent=2))
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
# Human-readable mode
|
|
39
|
+
click.echo(output, nl=False)
|
|
40
|
+
if use_time:
|
|
41
|
+
click.echo(f"[time] {elapsed:.6f}s")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def wrap_command(cmd):
|
|
45
|
+
"""Wrap a Click command so we can intercept execution with run_with_capture()."""
|
|
46
|
+
|
|
47
|
+
@functools.wraps(cmd.callback)
|
|
48
|
+
@click.pass_context
|
|
49
|
+
def wrapper(ctx, *args, **kwargs):
|
|
50
|
+
return run_with_capture(ctx, cmd.callback, *args, **kwargs)
|
|
51
|
+
|
|
52
|
+
return click.Command(
|
|
53
|
+
name=cmd.name,
|
|
54
|
+
callback=wrapper,
|
|
55
|
+
params=cmd.params,
|
|
56
|
+
help=cmd.help,
|
|
57
|
+
short_help=cmd.short_help,
|
|
58
|
+
epilog=cmd.epilog,
|
|
59
|
+
options_metavar=cmd.options_metavar,
|
|
60
|
+
add_help_option=cmd.add_help_option,
|
|
61
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.command(name="generate", help="Generate a TextFSM template from raw CLI output.")
|
|
8
|
+
@click.argument(
|
|
9
|
+
"input_file",
|
|
10
|
+
type=click.Path(exists=True, dir_okay=False, path_type=pathlib.Path),
|
|
11
|
+
)
|
|
12
|
+
@click.option(
|
|
13
|
+
"--provider",
|
|
14
|
+
"-p",
|
|
15
|
+
default="openai",
|
|
16
|
+
show_default=True,
|
|
17
|
+
help="Provider name to use for generation.",
|
|
18
|
+
)
|
|
19
|
+
@click.option(
|
|
20
|
+
"--model",
|
|
21
|
+
"-m",
|
|
22
|
+
default=None,
|
|
23
|
+
help="Optional model name override.",
|
|
24
|
+
)
|
|
25
|
+
def generate(input_file: pathlib.Path, provider: str, model: Optional[str]) -> None:
|
|
26
|
+
click.echo(f"Generating template from: {input_file}")
|
|
27
|
+
click.echo(f"Provider: {provider}")
|
|
28
|
+
click.echo(f"Model: {model}")
|
|
29
|
+
click.echo("Status: ok")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from ._utils import wrap_command # if you place wrappers in a helper file
|
|
4
|
+
from .generate_cmd import generate
|
|
5
|
+
from .providers_cmd import providers
|
|
6
|
+
from .quota_cmd import quota
|
|
7
|
+
from .version_cmd import version_cmd
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group(
|
|
11
|
+
name="textfsm-ai",
|
|
12
|
+
help="AI-powered TextFSM template generator with multi-provider routing.",
|
|
13
|
+
)
|
|
14
|
+
@click.option(
|
|
15
|
+
"--time", "time_flag", is_flag=True, help="Show execution time (human mode only)."
|
|
16
|
+
)
|
|
17
|
+
@click.option(
|
|
18
|
+
"--json", "json_flag", is_flag=True, help="Output results in JSON format."
|
|
19
|
+
)
|
|
20
|
+
@click.pass_context
|
|
21
|
+
def cli(ctx, time_flag, json_flag):
|
|
22
|
+
ctx.ensure_object(dict)
|
|
23
|
+
ctx.obj["time"] = time_flag
|
|
24
|
+
ctx.obj["json"] = json_flag
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Register wrapped subcommands
|
|
28
|
+
cli.add_command(wrap_command(generate))
|
|
29
|
+
cli.add_command(wrap_command(providers))
|
|
30
|
+
cli.add_command(wrap_command(quota))
|
|
31
|
+
cli.add_command(wrap_command(version_cmd))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def main():
|
|
35
|
+
cli()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
providers:
|
|
2
|
+
openai:
|
|
3
|
+
api_key: "${OPENAI_API_KEY}"
|
|
4
|
+
model: "gpt-5-mini"
|
|
5
|
+
daily_limit: 20000
|
|
6
|
+
monthly_limit: 300000
|
|
7
|
+
|
|
8
|
+
claude:
|
|
9
|
+
api_key: "${CLAUDE_API_KEY}"
|
|
10
|
+
model: "claude-3-haiku"
|
|
11
|
+
daily_limit: 20000
|
|
12
|
+
monthly_limit: 300000
|
|
13
|
+
|
|
14
|
+
gemini:
|
|
15
|
+
api_key: "${GEMINI_API_KEY}"
|
|
16
|
+
model: "gemini-2.5-flash"
|
|
17
|
+
daily_limit: 50000
|
|
18
|
+
monthly_limit: 500000
|
|
19
|
+
|
|
20
|
+
deepseek:
|
|
21
|
+
api_key: "${DEEPSEEK_API_KEY}"
|
|
22
|
+
model: "deepseek-v3.2-exp"
|
|
23
|
+
daily_limit: 50000
|
|
24
|
+
monthly_limit: 500000
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
from textfsm_ai.quota_manager import QuotaManager
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ClaudeProvider:
|
|
7
|
+
def __init__(self, api_key, model, daily_limit, monthly_limit):
|
|
8
|
+
self.api_key = api_key
|
|
9
|
+
self.model = model
|
|
10
|
+
self.url = "https://api.anthropic.com/v1/messages"
|
|
11
|
+
self.quota = QuotaManager("claude", daily_limit, monthly_limit)
|
|
12
|
+
|
|
13
|
+
def generate(self, prompt: str) -> str:
|
|
14
|
+
estimated_tokens = len(prompt.split()) * 2
|
|
15
|
+
if not self.quota.allowed(estimated_tokens):
|
|
16
|
+
raise RuntimeError("Claude quota exceeded")
|
|
17
|
+
|
|
18
|
+
headers = {
|
|
19
|
+
"x-api-key": self.api_key,
|
|
20
|
+
"anthropic-version": "2023-06-01",
|
|
21
|
+
}
|
|
22
|
+
payload = {
|
|
23
|
+
"model": self.model,
|
|
24
|
+
"max_tokens": 2048,
|
|
25
|
+
"messages": [{"role": "user", "content": prompt}],
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
resp = requests.post(self.url, json=payload, headers=headers, timeout=30)
|
|
29
|
+
resp.raise_for_status()
|
|
30
|
+
|
|
31
|
+
self.quota.add_tokens(estimated_tokens)
|
|
32
|
+
return resp.json()["content"][0]["text"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
from textfsm_ai.quota_manager import QuotaManager
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DeepSeekProvider:
|
|
7
|
+
def __init__(self, api_key, model, daily_limit, monthly_limit):
|
|
8
|
+
self.api_key = api_key
|
|
9
|
+
self.model = model
|
|
10
|
+
self.url = "https://api.deepseek.com/v1/chat/completions"
|
|
11
|
+
self.quota = QuotaManager("deepseek", daily_limit, monthly_limit)
|
|
12
|
+
|
|
13
|
+
def generate(self, prompt: str) -> str:
|
|
14
|
+
estimated_tokens = len(prompt.split()) * 2
|
|
15
|
+
if not self.quota.allowed(estimated_tokens):
|
|
16
|
+
raise RuntimeError("DeepSeek quota exceeded")
|
|
17
|
+
|
|
18
|
+
headers = {"Authorization": f"Bearer {self.api_key}"}
|
|
19
|
+
payload = {
|
|
20
|
+
"model": self.model,
|
|
21
|
+
"messages": [{"role": "user", "content": prompt}],
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
resp = requests.post(self.url, json=payload, headers=headers, timeout=30)
|
|
25
|
+
resp.raise_for_status()
|
|
26
|
+
|
|
27
|
+
self.quota.add_tokens(estimated_tokens)
|
|
28
|
+
return resp.json()["choices"][0]["message"]["content"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
from textfsm_ai.quota_manager import QuotaManager
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GeminiProvider:
|
|
7
|
+
def __init__(self, api_key, model, daily_limit, monthly_limit):
|
|
8
|
+
self.api_key = api_key
|
|
9
|
+
self.model = model
|
|
10
|
+
self.url = f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent"
|
|
11
|
+
self.quota = QuotaManager("gemini", daily_limit, monthly_limit)
|
|
12
|
+
|
|
13
|
+
def generate(self, prompt: str) -> str:
|
|
14
|
+
estimated_tokens = len(prompt.split()) * 2
|
|
15
|
+
if not self.quota.allowed(estimated_tokens):
|
|
16
|
+
raise RuntimeError("Gemini quota exceeded")
|
|
17
|
+
|
|
18
|
+
payload = {"contents": [{"parts": [{"text": prompt}]}]}
|
|
19
|
+
params = {"key": self.api_key}
|
|
20
|
+
|
|
21
|
+
resp = requests.post(self.url, json=payload, params=params, timeout=30)
|
|
22
|
+
resp.raise_for_status()
|
|
23
|
+
|
|
24
|
+
self.quota.add_tokens(estimated_tokens)
|
|
25
|
+
return resp.json()["candidates"][0]["content"]["parts"][0]["text"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
from textfsm_ai.quota_manager import QuotaManager
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class OpenAIProvider:
|
|
7
|
+
def __init__(self, api_key, model, daily_limit, monthly_limit):
|
|
8
|
+
self.api_key = api_key
|
|
9
|
+
self.model = model
|
|
10
|
+
self.url = "https://api.openai.com/v1/chat/completions"
|
|
11
|
+
self.quota = QuotaManager("openai", daily_limit, monthly_limit)
|
|
12
|
+
|
|
13
|
+
def generate(self, prompt: str) -> str:
|
|
14
|
+
estimated_tokens = len(prompt.split()) * 2
|
|
15
|
+
if not self.quota.allowed(estimated_tokens):
|
|
16
|
+
raise RuntimeError("OpenAI quota exceeded")
|
|
17
|
+
|
|
18
|
+
headers = {"Authorization": f"Bearer {self.api_key}"}
|
|
19
|
+
payload = {
|
|
20
|
+
"model": self.model,
|
|
21
|
+
"messages": [{"role": "user", "content": prompt}],
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
resp = requests.post(self.url, json=payload, headers=headers, timeout=30)
|
|
25
|
+
resp.raise_for_status()
|
|
26
|
+
|
|
27
|
+
self.quota.add_tokens(estimated_tokens)
|
|
28
|
+
return resp.json()["choices"][0]["message"]["content"]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import time
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class QuotaManager:
|
|
7
|
+
def __init__(self, provider_name: str, daily_limit: int, monthly_limit: int):
|
|
8
|
+
self.provider = provider_name
|
|
9
|
+
self.daily_limit = daily_limit
|
|
10
|
+
self.monthly_limit = monthly_limit
|
|
11
|
+
|
|
12
|
+
self.path = Path.home() / f".textfsm_ai_usage_{provider_name}.json"
|
|
13
|
+
self._load()
|
|
14
|
+
|
|
15
|
+
def _load(self):
|
|
16
|
+
if self.path.exists():
|
|
17
|
+
with open(self.path, "r") as f:
|
|
18
|
+
self.data = json.load(f)
|
|
19
|
+
else:
|
|
20
|
+
self.data = {
|
|
21
|
+
"day": self._today(),
|
|
22
|
+
"month": self._month(),
|
|
23
|
+
"daily_tokens": 0,
|
|
24
|
+
"monthly_tokens": 0,
|
|
25
|
+
"just_reset_month": False,
|
|
26
|
+
}
|
|
27
|
+
self._save()
|
|
28
|
+
|
|
29
|
+
def _save(self):
|
|
30
|
+
with open(self.path, "w") as f:
|
|
31
|
+
json.dump(self.data, f)
|
|
32
|
+
|
|
33
|
+
def _today(self):
|
|
34
|
+
return time.strftime("%Y-%m-%d")
|
|
35
|
+
|
|
36
|
+
def _month(self):
|
|
37
|
+
return time.strftime("%Y-%m")
|
|
38
|
+
|
|
39
|
+
def _maybe_reset(self):
|
|
40
|
+
today = self._today()
|
|
41
|
+
month = self._month()
|
|
42
|
+
|
|
43
|
+
# Monthly reset
|
|
44
|
+
if self.data["month"] != month:
|
|
45
|
+
self.data["month"] = month
|
|
46
|
+
self.data["monthly_tokens"] = 0
|
|
47
|
+
self.data["day"] = today
|
|
48
|
+
self.data["daily_tokens"] = 0
|
|
49
|
+
self.data["just_reset_month"] = True
|
|
50
|
+
self._save()
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
# Daily reset
|
|
54
|
+
if self.data["day"] != today:
|
|
55
|
+
self.data["day"] = today
|
|
56
|
+
self.data["daily_tokens"] = 0
|
|
57
|
+
self.data["just_reset_month"] = False
|
|
58
|
+
self._save()
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
def allowed(self, count: int) -> bool:
|
|
62
|
+
self._maybe_reset()
|
|
63
|
+
|
|
64
|
+
# Monthly limit always enforced
|
|
65
|
+
if self.data["monthly_tokens"] + count > self.monthly_limit:
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
# Daily limit skipped ONCE after monthly reset
|
|
69
|
+
if not self.data.get("just_reset_month", False):
|
|
70
|
+
if self.data["daily_tokens"] + count > self.daily_limit:
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
def add_tokens(self, count: int):
|
|
76
|
+
self._maybe_reset()
|
|
77
|
+
|
|
78
|
+
# After first request of new month, daily limit applies again
|
|
79
|
+
if self.data.get("just_reset_month", False):
|
|
80
|
+
self.data["just_reset_month"] = False
|
|
81
|
+
|
|
82
|
+
self.data["daily_tokens"] += count
|
|
83
|
+
self.data["monthly_tokens"] += count
|
|
84
|
+
self._save()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: textfsm-ai
|
|
3
|
+
Version: 0.1.19
|
|
4
|
+
Summary: AI-powered TextFSM template generator with multi-provider routing
|
|
5
|
+
Author-email: Tuyen Mathew Duong <tuyen@geekstrident.com>
|
|
6
|
+
Maintainer-email: Tuyen Mathew Duong <tuyen@geekstrident.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/Geeks-Trident-LLC/textfsm-ai
|
|
9
|
+
Project-URL: Repository, https://github.com/Geeks-Trident-LLC/textfsm-ai
|
|
10
|
+
Project-URL: Issues, https://github.com/Geeks-Trident-LLC/textfsm-ai/issues
|
|
11
|
+
Project-URL: Documentation, https://geeks-trident-llc.github.io/textfsm-ai/latest/
|
|
12
|
+
Keywords: textfsm,network automation,cli parsing,template generator,ai,llm,openai,claude,gemini,deepseek,router,quota manager
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Information Technology
|
|
16
|
+
Classifier: Intended Audience :: System Administrators
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
23
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
24
|
+
Classifier: Topic :: Text Processing
|
|
25
|
+
Classifier: Topic :: Utilities
|
|
26
|
+
Classifier: Operating System :: OS Independent
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Requires-Dist: PyYAML>=6.0
|
|
31
|
+
Requires-Dist: requests>=2.31
|
|
32
|
+
Requires-Dist: click>=8.1
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: build; extra == "dev"
|
|
35
|
+
Requires-Dist: twine; extra == "dev"
|
|
36
|
+
Provides-Extra: test
|
|
37
|
+
Requires-Dist: pytest; extra == "test"
|
|
38
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
39
|
+
Provides-Extra: lint
|
|
40
|
+
Requires-Dist: ruff; extra == "lint"
|
|
41
|
+
Provides-Extra: all
|
|
42
|
+
Requires-Dist: build; extra == "all"
|
|
43
|
+
Requires-Dist: twine; extra == "all"
|
|
44
|
+
Requires-Dist: pytest; extra == "all"
|
|
45
|
+
Requires-Dist: pytest-cov; extra == "all"
|
|
46
|
+
Requires-Dist: ruff; extra == "all"
|
|
47
|
+
Dynamic: license-file
|
|
48
|
+
|
|
49
|
+
# textfsm-ai
|
|
50
|
+
|
|
51
|
+
**AI‑powered TextFSM template generation, parsing assistance, and smart log extraction.**
|
|
52
|
+
|
|
53
|
+
`textfsm-ai` brings modern LLM intelligence to traditional TextFSM workflows.
|
|
54
|
+
It helps you automatically generate templates, validate patterns, explain parsing logic, and accelerate network automation development.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 🚀 Features
|
|
59
|
+
|
|
60
|
+
- **AI‑Powered Template Generation** — Turn raw CLI output into production‑ready TextFSM templates in seconds.
|
|
61
|
+
- **Smart Validation & Refinement** — Automatically verify template correctness and refine ambiguous patterns with AI assistance.
|
|
62
|
+
- **Built‑In Golden Master Testing** — Lock in deterministic parsing behavior and detect regressions instantly.
|
|
63
|
+
- **Flexible Multi‑Provider AI Routing** — Use the best AI model for each task with automatic routing across supported cloud providers.
|
|
64
|
+
|
|
65
|
+
---
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
textfsm_ai/__init__.py
|
|
6
|
+
textfsm_ai/ai_router.py
|
|
7
|
+
textfsm_ai/api.py
|
|
8
|
+
textfsm_ai/config.yaml
|
|
9
|
+
textfsm_ai/config_loader.py
|
|
10
|
+
textfsm_ai/quota_manager.py
|
|
11
|
+
textfsm_ai.egg-info/PKG-INFO
|
|
12
|
+
textfsm_ai.egg-info/SOURCES.txt
|
|
13
|
+
textfsm_ai.egg-info/dependency_links.txt
|
|
14
|
+
textfsm_ai.egg-info/entry_points.txt
|
|
15
|
+
textfsm_ai.egg-info/requires.txt
|
|
16
|
+
textfsm_ai.egg-info/top_level.txt
|
|
17
|
+
textfsm_ai/cli/__init__.py
|
|
18
|
+
textfsm_ai/cli/_utils.py
|
|
19
|
+
textfsm_ai/cli/generate_cmd.py
|
|
20
|
+
textfsm_ai/cli/providers_cmd.py
|
|
21
|
+
textfsm_ai/cli/quota_cmd.py
|
|
22
|
+
textfsm_ai/cli/top.py
|
|
23
|
+
textfsm_ai/cli/version_cmd.py
|
|
24
|
+
textfsm_ai/providers/__init__.py
|
|
25
|
+
textfsm_ai/providers/claude_provider.py
|
|
26
|
+
textfsm_ai/providers/deepseek_provider.py
|
|
27
|
+
textfsm_ai/providers/gemini_provider.py
|
|
28
|
+
textfsm_ai/providers/openai_provider.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
textfsm_ai
|