agenticstackfile 0.1.3__tar.gz → 0.2.0__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.
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/PKG-INFO +2 -1
- agenticstackfile-0.2.0/agenticstack/cli.py +110 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstack/config.py +9 -5
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstack/core.py +1 -1
- agenticstackfile-0.2.0/agenticstack/scheduler.py +41 -0
- agenticstackfile-0.2.0/agenticstack/watcher.py +64 -0
- agenticstackfile-0.2.0/agenticstack/writer.py +23 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstackfile.egg-info/PKG-INFO +2 -1
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstackfile.egg-info/SOURCES.txt +2 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstackfile.egg-info/requires.txt +1 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/pyproject.toml +2 -2
- agenticstackfile-0.1.3/agenticstack/cli.py +0 -45
- agenticstackfile-0.1.3/agenticstack/writer.py +0 -5
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/README.md +0 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstack/__init__.py +0 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstack/analyzer/__init__.py +0 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstack/analyzer/base.py +0 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstack/analyzer/python.py +0 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstack/errors.py +0 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstack/formatter/__init__.py +0 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstack/formatter/static.py +0 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstack/prompt.py +0 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstack/providers/__init__.py +0 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstack/providers/anthropic.py +0 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstack/providers/base.py +0 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstack/providers/factory.py +0 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstack/providers/openai.py +0 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstackfile.egg-info/dependency_links.txt +0 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstackfile.egg-info/entry_points.txt +0 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstackfile.egg-info/top_level.txt +0 -0
- {agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agenticstackfile
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Instant codebase map for AI agents — understand any project before making changes
|
|
5
5
|
Author-email: Rajat Handa <handarajat111@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -14,6 +14,7 @@ Requires-Dist: openai>=1.0.0; extra == "openai"
|
|
|
14
14
|
Provides-Extra: all
|
|
15
15
|
Requires-Dist: anthropic>=0.20.0; extra == "all"
|
|
16
16
|
Requires-Dist: openai>=1.0.0; extra == "all"
|
|
17
|
+
Requires-Dist: watchdog>=3.0.0; extra == "all"
|
|
17
18
|
|
|
18
19
|
# AgenticStack
|
|
19
20
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from .config import create_default_config, config_exists, write_config, read_config, create_default_ignore_file
|
|
5
|
+
from .core import run
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _check_staleness(config: dict) -> None:
|
|
9
|
+
output_file = Path(config.get("output_file", "AgenticStack.txt"))
|
|
10
|
+
if not output_file.exists():
|
|
11
|
+
return
|
|
12
|
+
age_seconds = (datetime.now() - datetime.fromtimestamp(output_file.stat().st_mtime)).total_seconds()
|
|
13
|
+
thresholds = {"realtime": 300, "hourly": 3600, "daily": 86400}
|
|
14
|
+
sync_time = config.get("sync_time", "false")
|
|
15
|
+
threshold = thresholds.get(sync_time)
|
|
16
|
+
if threshold and age_seconds > threshold:
|
|
17
|
+
hours = int(age_seconds // 3600)
|
|
18
|
+
minutes = int((age_seconds % 3600) // 60)
|
|
19
|
+
click.echo(f"WARNING: AgenticStack.txt was last updated {hours}h {minutes}m ago. Consider running 'agenticstack update'.")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _start_background_services(config: dict, root_path: Path) -> None:
|
|
23
|
+
if config.get("sync_trigger", "false").lower() != "true":
|
|
24
|
+
return
|
|
25
|
+
sync_time = config.get("sync_time", "hourly")
|
|
26
|
+
try:
|
|
27
|
+
from .watcher import start_watcher
|
|
28
|
+
from .scheduler import start_scheduler
|
|
29
|
+
start_watcher(root_path)
|
|
30
|
+
start_scheduler(root_path, sync_time)
|
|
31
|
+
click.echo(f"AgenticStack: Auto sync enabled ({sync_time})")
|
|
32
|
+
except ImportError as e:
|
|
33
|
+
click.echo(f"WARNING: {e}")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@click.group()
|
|
37
|
+
def main():
|
|
38
|
+
"""AgenticStack — instant codebase map for AI agents."""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@main.command()
|
|
43
|
+
@click.option("--api-key", default=None, help="Your LLM provider API key")
|
|
44
|
+
@click.option("--provider", default=None, help="Provider: anthropic or openai")
|
|
45
|
+
@click.option("--model", default=None, help="Model: default, fast, or smart")
|
|
46
|
+
def init(api_key, provider, model):
|
|
47
|
+
"""Initialize AgenticStack in this project."""
|
|
48
|
+
if config_exists():
|
|
49
|
+
click.echo("AgenticStack: Config already exists. Use 'agenticstack update' to refresh.")
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
create_default_config()
|
|
53
|
+
click.echo("AgenticStack: Created .agenticstack.ini")
|
|
54
|
+
create_default_ignore_file()
|
|
55
|
+
click.echo("AgenticStack: Created .agenticstackignore")
|
|
56
|
+
|
|
57
|
+
if api_key:
|
|
58
|
+
write_config("api_key", api_key)
|
|
59
|
+
if provider:
|
|
60
|
+
write_config("provider", provider)
|
|
61
|
+
if model:
|
|
62
|
+
write_config("model", model)
|
|
63
|
+
|
|
64
|
+
config = read_config()
|
|
65
|
+
_start_background_services(config, Path.cwd())
|
|
66
|
+
run(Path.cwd())
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@main.command()
|
|
70
|
+
def update():
|
|
71
|
+
"""Refresh AgenticStack.txt for this project."""
|
|
72
|
+
if not config_exists():
|
|
73
|
+
click.echo("AgenticStack: No config found. Run 'agenticstack init' first.")
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
config = read_config()
|
|
77
|
+
_check_staleness(config)
|
|
78
|
+
_start_background_services(config, Path.cwd())
|
|
79
|
+
run(Path.cwd())
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@main.command()
|
|
83
|
+
def watch():
|
|
84
|
+
"""Watch for file changes and auto sync AgenticStack.txt."""
|
|
85
|
+
if not config_exists():
|
|
86
|
+
click.echo("AgenticStack: No config found. Run 'agenticstack init' first.")
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
config = read_config()
|
|
90
|
+
sync_time = config.get("sync_time", "hourly")
|
|
91
|
+
root_path = Path.cwd()
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
from .watcher import start_watcher
|
|
95
|
+
from .scheduler import start_scheduler
|
|
96
|
+
import time
|
|
97
|
+
|
|
98
|
+
start_watcher(root_path)
|
|
99
|
+
start_scheduler(root_path, sync_time)
|
|
100
|
+
|
|
101
|
+
click.echo(f"AgenticStack: Watching for changes ({sync_time} sync)...")
|
|
102
|
+
click.echo("Press Ctrl+C to stop.")
|
|
103
|
+
|
|
104
|
+
while True:
|
|
105
|
+
time.sleep(1)
|
|
106
|
+
|
|
107
|
+
except ImportError as e:
|
|
108
|
+
click.echo(f"ERROR: {e}")
|
|
109
|
+
except KeyboardInterrupt:
|
|
110
|
+
click.echo("\nAgenticStack: Watch stopped.")
|
|
@@ -11,6 +11,8 @@ DEFAULTS = {
|
|
|
11
11
|
"language": "auto",
|
|
12
12
|
"output_file": "AgenticStack.txt",
|
|
13
13
|
"depth": "standard",
|
|
14
|
+
"sync_trigger": "false",
|
|
15
|
+
"sync_time": "hourly",
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
|
|
@@ -50,16 +52,18 @@ def write_config(key: str, value: str) -> None:
|
|
|
50
52
|
|
|
51
53
|
def _add_to_gitignore() -> None:
|
|
52
54
|
gitignore_path = Path.cwd() / ".gitignore"
|
|
53
|
-
|
|
55
|
+
entries = [CONFIG_FILE, ".agenticstack_pending"]
|
|
54
56
|
|
|
55
57
|
if gitignore_path.exists():
|
|
56
58
|
content = gitignore_path.read_text()
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
with open(gitignore_path, "a") as f:
|
|
60
|
+
for entry in entries:
|
|
61
|
+
if entry not in content:
|
|
62
|
+
f.write(f"\n{entry}\n")
|
|
60
63
|
else:
|
|
61
64
|
with open(gitignore_path, "w") as f:
|
|
62
|
-
|
|
65
|
+
for entry in entries:
|
|
66
|
+
f.write(f"{entry}\n")
|
|
63
67
|
|
|
64
68
|
|
|
65
69
|
def create_default_ignore_file() -> None:
|
|
@@ -50,5 +50,5 @@ def run(root_path: Path = None) -> None:
|
|
|
50
50
|
final_output = static_output
|
|
51
51
|
|
|
52
52
|
output_file = config.get("output_file", "AgenticStack.txt")
|
|
53
|
-
write_output(final_output, Path(output_file))
|
|
53
|
+
write_output(final_output, Path(output_file), sync_time=config.get("sync_time", "false"))
|
|
54
54
|
print(f"AgenticStack: Done. Output written to {output_file}")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
SYNC_INTERVALS = {
|
|
6
|
+
"realtime": 300,
|
|
7
|
+
"hourly": 3600,
|
|
8
|
+
"daily": 86400,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def start_scheduler(root_path: Path, sync_time: str) -> None:
|
|
13
|
+
interval = SYNC_INTERVALS.get(sync_time)
|
|
14
|
+
|
|
15
|
+
if not interval:
|
|
16
|
+
print(f"AgenticStack: Scheduler — unknown sync_time '{sync_time}', skipping.")
|
|
17
|
+
return
|
|
18
|
+
|
|
19
|
+
print(f"AgenticStack: Scheduler started — will sync every {interval} seconds.")
|
|
20
|
+
|
|
21
|
+
def sync_loop():
|
|
22
|
+
from .watcher import has_pending_changes, clear_pending
|
|
23
|
+
from .core import run
|
|
24
|
+
|
|
25
|
+
print("AgenticStack: Scheduler tick — checking for pending changes...")
|
|
26
|
+
|
|
27
|
+
if has_pending_changes(root_path):
|
|
28
|
+
print("AgenticStack: Pending changes detected — syncing...")
|
|
29
|
+
clear_pending(root_path)
|
|
30
|
+
run(root_path)
|
|
31
|
+
print("AgenticStack: Sync complete.")
|
|
32
|
+
else:
|
|
33
|
+
print("AgenticStack: No pending changes — skipping.")
|
|
34
|
+
|
|
35
|
+
timer = threading.Timer(interval, sync_loop)
|
|
36
|
+
timer.daemon = True
|
|
37
|
+
timer.start()
|
|
38
|
+
|
|
39
|
+
timer = threading.Timer(interval, sync_loop)
|
|
40
|
+
timer.daemon = True
|
|
41
|
+
timer.start()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
PENDING_FILE = ".agenticstack_pending"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def record_change(filepath: str, root_path: Path) -> None:
|
|
9
|
+
pending_path = root_path / PENDING_FILE
|
|
10
|
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
11
|
+
with open(pending_path, "a") as f:
|
|
12
|
+
f.write(f"{timestamp} | {filepath}\n")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def has_pending_changes(root_path: Path) -> bool:
|
|
16
|
+
pending_path = root_path / PENDING_FILE
|
|
17
|
+
if not pending_path.exists():
|
|
18
|
+
return False
|
|
19
|
+
return pending_path.read_text().strip() != ""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def clear_pending(root_path: Path) -> None:
|
|
23
|
+
pending_path = root_path / PENDING_FILE
|
|
24
|
+
if pending_path.exists():
|
|
25
|
+
pending_path.write_text("")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def start_watcher(root_path: Path) -> None:
|
|
29
|
+
try:
|
|
30
|
+
from watchdog.observers import Observer
|
|
31
|
+
from watchdog.events import FileSystemEventHandler
|
|
32
|
+
IGNORE_FILES = {
|
|
33
|
+
".agenticstack_pending",
|
|
34
|
+
".agenticstack.ini",
|
|
35
|
+
"AgenticStack.txt",
|
|
36
|
+
".agenticstackignore",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class ChangeHandler(FileSystemEventHandler):
|
|
40
|
+
def _should_ignore(self, path: str) -> bool:
|
|
41
|
+
filename = Path(path).name
|
|
42
|
+
return filename in IGNORE_FILES
|
|
43
|
+
|
|
44
|
+
def on_modified(self, event):
|
|
45
|
+
if not event.is_directory and not self._should_ignore(event.src_path):
|
|
46
|
+
record_change(event.src_path, root_path)
|
|
47
|
+
print(f"AgenticStack: Change detected — {event.src_path}")
|
|
48
|
+
|
|
49
|
+
def on_created(self, event):
|
|
50
|
+
if not event.is_directory and not self._should_ignore(event.src_path):
|
|
51
|
+
record_change(event.src_path, root_path)
|
|
52
|
+
print(f"AgenticStack: New file detected — {event.src_path}")
|
|
53
|
+
|
|
54
|
+
observer = Observer()
|
|
55
|
+
observer.schedule(ChangeHandler(), str(root_path), recursive=True)
|
|
56
|
+
observer.daemon = True
|
|
57
|
+
observer.start()
|
|
58
|
+
print(f"AgenticStack: Watcher started on {root_path}")
|
|
59
|
+
|
|
60
|
+
except ImportError:
|
|
61
|
+
raise ImportError(
|
|
62
|
+
"watchdog not installed. "
|
|
63
|
+
"Run: pip install 'agenticstackfile[watch]'"
|
|
64
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
SYNC_INTERVALS = {
|
|
5
|
+
"realtime": "every 2 minutes",
|
|
6
|
+
"hourly": "every 1 hour",
|
|
7
|
+
"daily": "every 24 hours",
|
|
8
|
+
"false": "manual only",
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
def write_output(content: str, output_path: Path, sync_time: str = "false") -> None:
|
|
12
|
+
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
13
|
+
sync_label = SYNC_INTERVALS.get(sync_time, "manual only")
|
|
14
|
+
|
|
15
|
+
header = f"""============================================================
|
|
16
|
+
AGENTICSTACK — CODEBASE MAP
|
|
17
|
+
Generated: {now}
|
|
18
|
+
Auto sync: {sync_label}
|
|
19
|
+
WARNING: Run 'agenticstack update' to force refresh anytime
|
|
20
|
+
============================================================
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
output_path.write_text(header + content, encoding="utf-8")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agenticstackfile
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Instant codebase map for AI agents — understand any project before making changes
|
|
5
5
|
Author-email: Rajat Handa <handarajat111@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -14,6 +14,7 @@ Requires-Dist: openai>=1.0.0; extra == "openai"
|
|
|
14
14
|
Provides-Extra: all
|
|
15
15
|
Requires-Dist: anthropic>=0.20.0; extra == "all"
|
|
16
16
|
Requires-Dist: openai>=1.0.0; extra == "all"
|
|
17
|
+
Requires-Dist: watchdog>=3.0.0; extra == "all"
|
|
17
18
|
|
|
18
19
|
# AgenticStack
|
|
19
20
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agenticstackfile"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Instant codebase map for AI agents — understand any project before making changes"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -19,7 +19,7 @@ dependencies = [
|
|
|
19
19
|
[project.optional-dependencies]
|
|
20
20
|
anthropic = ["anthropic>=0.20.0"]
|
|
21
21
|
openai = ["openai>=1.0.0"]
|
|
22
|
-
all = ["anthropic>=0.20.0", "openai>=1.0.0"]
|
|
22
|
+
all = ["anthropic>=0.20.0", "openai>=1.0.0", "watchdog>=3.0.0"]
|
|
23
23
|
|
|
24
24
|
[project.scripts]
|
|
25
25
|
agenticstack = "agenticstack.cli:main"
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import click
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
from .config import create_default_config, config_exists, write_config, read_config, create_default_ignore_file
|
|
4
|
-
from .core import run
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@click.group()
|
|
8
|
-
def main():
|
|
9
|
-
"""AgenticStack — instant codebase map for AI agents."""
|
|
10
|
-
pass
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@main.command()
|
|
14
|
-
@click.option("--api-key", default=None, help="Your LLM provider API key")
|
|
15
|
-
@click.option("--provider", default=None, help="Provider: anthropic or openai")
|
|
16
|
-
@click.option("--model", default=None, help="Model: default, fast, or smart")
|
|
17
|
-
def init(api_key, provider, model):
|
|
18
|
-
"""Initialize AgenticStack in this project."""
|
|
19
|
-
if config_exists():
|
|
20
|
-
click.echo("AgenticStack: Config already exists. Use 'agenticstack update' to refresh.")
|
|
21
|
-
return
|
|
22
|
-
|
|
23
|
-
create_default_config()
|
|
24
|
-
click.echo("AgenticStack: Created .agenticstack.ini")
|
|
25
|
-
create_default_ignore_file()
|
|
26
|
-
click.echo("AgenticStack: Created .agenticstackignore")
|
|
27
|
-
|
|
28
|
-
if api_key:
|
|
29
|
-
write_config("api_key", api_key)
|
|
30
|
-
if provider:
|
|
31
|
-
write_config("provider", provider)
|
|
32
|
-
if model:
|
|
33
|
-
write_config("model", model)
|
|
34
|
-
|
|
35
|
-
run(Path.cwd())
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
@main.command()
|
|
39
|
-
def update():
|
|
40
|
-
"""Refresh AgenticStack.txt for this project."""
|
|
41
|
-
if not config_exists():
|
|
42
|
-
click.echo("AgenticStack: No config found. Run 'agenticstack init' first.")
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
run(Path.cwd())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstackfile.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{agenticstackfile-0.1.3 → agenticstackfile-0.2.0}/agenticstackfile.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|