agenticstackfile 0.1.2__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.
Files changed (31) hide show
  1. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/PKG-INFO +2 -1
  2. agenticstackfile-0.2.0/agenticstack/cli.py +110 -0
  3. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstack/config.py +9 -6
  4. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstack/core.py +1 -1
  5. agenticstackfile-0.2.0/agenticstack/scheduler.py +41 -0
  6. agenticstackfile-0.2.0/agenticstack/watcher.py +64 -0
  7. agenticstackfile-0.2.0/agenticstack/writer.py +23 -0
  8. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstackfile.egg-info/PKG-INFO +2 -1
  9. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstackfile.egg-info/SOURCES.txt +2 -0
  10. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstackfile.egg-info/requires.txt +1 -0
  11. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/pyproject.toml +2 -2
  12. agenticstackfile-0.1.2/agenticstack/cli.py +0 -45
  13. agenticstackfile-0.1.2/agenticstack/writer.py +0 -5
  14. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/README.md +0 -0
  15. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstack/__init__.py +0 -0
  16. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstack/analyzer/__init__.py +0 -0
  17. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstack/analyzer/base.py +0 -0
  18. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstack/analyzer/python.py +0 -0
  19. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstack/errors.py +0 -0
  20. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstack/formatter/__init__.py +0 -0
  21. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstack/formatter/static.py +0 -0
  22. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstack/prompt.py +0 -0
  23. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstack/providers/__init__.py +0 -0
  24. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstack/providers/anthropic.py +0 -0
  25. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstack/providers/base.py +0 -0
  26. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstack/providers/factory.py +0 -0
  27. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstack/providers/openai.py +0 -0
  28. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstackfile.egg-info/dependency_links.txt +0 -0
  29. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstackfile.egg-info/entry_points.txt +0 -0
  30. {agenticstackfile-0.1.2 → agenticstackfile-0.2.0}/agenticstackfile.egg-info/top_level.txt +0 -0
  31. {agenticstackfile-0.1.2 → 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.1.2
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
- entry = CONFIG_FILE
55
+ entries = [CONFIG_FILE, ".agenticstack_pending"]
54
56
 
55
57
  if gitignore_path.exists():
56
58
  content = gitignore_path.read_text()
57
- if entry not in content:
58
- with open(gitignore_path, "a") as f:
59
- f.write(f"\n{entry}\n")
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
- f.write(f"{entry}\n")
65
+ for entry in entries:
66
+ f.write(f"{entry}\n")
63
67
 
64
68
 
65
69
  def create_default_ignore_file() -> None:
@@ -77,7 +81,6 @@ def create_default_ignore_file() -> None:
77
81
  env
78
82
 
79
83
  # Test directories
80
- test_project
81
84
  tests
82
85
 
83
86
  # Build artifacts
@@ -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.1.2
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
 
@@ -6,6 +6,8 @@ agenticstack/config.py
6
6
  agenticstack/core.py
7
7
  agenticstack/errors.py
8
8
  agenticstack/prompt.py
9
+ agenticstack/scheduler.py
10
+ agenticstack/watcher.py
9
11
  agenticstack/writer.py
10
12
  agenticstack/analyzer/__init__.py
11
13
  agenticstack/analyzer/base.py
@@ -3,6 +3,7 @@ click>=8.0.0
3
3
  [all]
4
4
  anthropic>=0.20.0
5
5
  openai>=1.0.0
6
+ watchdog>=3.0.0
6
7
 
7
8
  [anthropic]
8
9
  anthropic>=0.20.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agenticstackfile"
7
- version = "0.1.2"
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())
@@ -1,5 +0,0 @@
1
- from pathlib import Path
2
-
3
-
4
- def write_output(content: str, output_path: Path) -> None:
5
- output_path.write_text(content, encoding="utf-8")