agentpack-cli 0.1.0__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.
Files changed (80) hide show
  1. agentpack/__init__.py +3 -0
  2. agentpack/adapters/__init__.py +0 -0
  3. agentpack/adapters/base.py +22 -0
  4. agentpack/adapters/claude.py +32 -0
  5. agentpack/adapters/codex.py +26 -0
  6. agentpack/adapters/cursor.py +29 -0
  7. agentpack/adapters/generic.py +18 -0
  8. agentpack/adapters/windsurf.py +26 -0
  9. agentpack/analysis/__init__.py +0 -0
  10. agentpack/analysis/dependency_graph.py +80 -0
  11. agentpack/analysis/go_imports.py +32 -0
  12. agentpack/analysis/java_imports.py +19 -0
  13. agentpack/analysis/js_ts_imports.py +53 -0
  14. agentpack/analysis/python_imports.py +45 -0
  15. agentpack/analysis/ranking.py +400 -0
  16. agentpack/analysis/rust_imports.py +32 -0
  17. agentpack/analysis/symbols.py +154 -0
  18. agentpack/analysis/tests.py +30 -0
  19. agentpack/application/__init__.py +0 -0
  20. agentpack/application/pack_service.py +352 -0
  21. agentpack/cli.py +33 -0
  22. agentpack/commands/__init__.py +0 -0
  23. agentpack/commands/_shared.py +13 -0
  24. agentpack/commands/benchmark.py +302 -0
  25. agentpack/commands/claude_cmd.py +55 -0
  26. agentpack/commands/diff.py +46 -0
  27. agentpack/commands/doctor.py +185 -0
  28. agentpack/commands/explain.py +238 -0
  29. agentpack/commands/init.py +79 -0
  30. agentpack/commands/install.py +252 -0
  31. agentpack/commands/monitor.py +105 -0
  32. agentpack/commands/pack.py +188 -0
  33. agentpack/commands/scan.py +51 -0
  34. agentpack/commands/session.py +204 -0
  35. agentpack/commands/stats.py +138 -0
  36. agentpack/commands/status.py +37 -0
  37. agentpack/commands/summarize.py +64 -0
  38. agentpack/commands/watch.py +185 -0
  39. agentpack/core/__init__.py +0 -0
  40. agentpack/core/bootstrap.py +46 -0
  41. agentpack/core/cache.py +41 -0
  42. agentpack/core/config.py +101 -0
  43. agentpack/core/context_pack.py +222 -0
  44. agentpack/core/diff.py +40 -0
  45. agentpack/core/git.py +145 -0
  46. agentpack/core/git_hooks.py +8 -0
  47. agentpack/core/global_install.py +14 -0
  48. agentpack/core/ignore.py +66 -0
  49. agentpack/core/merkle.py +8 -0
  50. agentpack/core/models.py +115 -0
  51. agentpack/core/redactor.py +99 -0
  52. agentpack/core/scanner.py +150 -0
  53. agentpack/core/snapshot.py +60 -0
  54. agentpack/core/token_estimator.py +26 -0
  55. agentpack/core/vscode_tasks.py +5 -0
  56. agentpack/data/agentpack.md +160 -0
  57. agentpack/installers/__init__.py +0 -0
  58. agentpack/installers/claude.py +160 -0
  59. agentpack/installers/codex.py +54 -0
  60. agentpack/installers/cursor.py +76 -0
  61. agentpack/installers/windsurf.py +50 -0
  62. agentpack/integrations/__init__.py +0 -0
  63. agentpack/integrations/git_hooks.py +109 -0
  64. agentpack/integrations/global_install.py +221 -0
  65. agentpack/integrations/vscode_tasks.py +85 -0
  66. agentpack/renderers/__init__.py +3 -0
  67. agentpack/renderers/compact.py +75 -0
  68. agentpack/renderers/markdown.py +144 -0
  69. agentpack/renderers/receipts.py +10 -0
  70. agentpack/session/__init__.py +33 -0
  71. agentpack/session/state.py +105 -0
  72. agentpack/summaries/__init__.py +0 -0
  73. agentpack/summaries/base.py +42 -0
  74. agentpack/summaries/llm.py +100 -0
  75. agentpack/summaries/offline.py +97 -0
  76. agentpack_cli-0.1.0.dist-info/METADATA +1391 -0
  77. agentpack_cli-0.1.0.dist-info/RECORD +80 -0
  78. agentpack_cli-0.1.0.dist-info/WHEEL +4 -0
  79. agentpack_cli-0.1.0.dist-info/entry_points.txt +2 -0
  80. agentpack_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
agentpack/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """AgentPack — token-aware context packing for AI coding agents."""
2
+
3
+ __version__ = "0.1.0"
File without changes
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from pathlib import Path
5
+
6
+ from agentpack.core.models import ContextPack
7
+
8
+
9
+ class BaseAdapter(ABC):
10
+ @abstractmethod
11
+ def output_path(self, root: Path) -> Path:
12
+ ...
13
+
14
+ @abstractmethod
15
+ def render(self, pack: ContextPack) -> str:
16
+ ...
17
+
18
+ def write(self, pack: ContextPack, root: Path) -> Path:
19
+ out = self.output_path(root)
20
+ out.parent.mkdir(parents=True, exist_ok=True)
21
+ out.write_text(self.render(pack))
22
+ return out
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from agentpack.adapters.base import BaseAdapter
6
+ from agentpack.core.models import ContextPack
7
+ from agentpack.renderers.markdown import render_claude
8
+
9
+ # Re-export installer symbols for backward compatibility
10
+ from agentpack.installers.claude import ( # noqa: F401
11
+ ClaudeInstaller,
12
+ _AGENTPACK_BLOCK,
13
+ _BLOCK_RE,
14
+ )
15
+
16
+
17
+ class ClaudeAdapter(BaseAdapter):
18
+ def __init__(self, output: str = ".agentpack/context.claude.md"):
19
+ self._output = output
20
+
21
+ def output_path(self, root: Path) -> Path:
22
+ return root / self._output
23
+
24
+ def render(self, pack: ContextPack) -> str:
25
+ return render_claude(pack)
26
+
27
+ # Delegating install methods — kept for backward compat with any callers using adapter directly
28
+ def patch_claude_md(self, root: Path) -> str:
29
+ return ClaudeInstaller().patch_claude_md(root)
30
+
31
+ def patch_claude_settings(self, root: Path, global_install: bool = False) -> str:
32
+ return ClaudeInstaller().patch_claude_settings(root, global_install)
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from agentpack.adapters.base import BaseAdapter
6
+ from agentpack.core.models import ContextPack
7
+ from agentpack.renderers.markdown import render_generic
8
+ from agentpack.installers.codex import CodexInstaller # noqa: F401
9
+
10
+
11
+ class CodexAdapter(BaseAdapter):
12
+ def __init__(self, output: str = ".agentpack/context.md"):
13
+ self._output = output
14
+
15
+ def output_path(self, root: Path) -> Path:
16
+ return root / self._output
17
+
18
+ def render(self, pack: ContextPack) -> str:
19
+ return render_generic(pack)
20
+
21
+ # Delegating install methods — kept for backward compat
22
+ def patch_agents_md(self, root: Path) -> str:
23
+ return CodexInstaller().patch_agents_md(root)
24
+
25
+ def install_auto_repack(self, root: Path) -> dict[str, str]:
26
+ return CodexInstaller().install_auto_repack(root)
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from agentpack.adapters.base import BaseAdapter
6
+ from agentpack.core.models import ContextPack
7
+ from agentpack.renderers.markdown import render_generic
8
+ from agentpack.installers.cursor import CursorInstaller # noqa: F401
9
+
10
+
11
+ class CursorAdapter(BaseAdapter):
12
+ def __init__(self, output: str = ".agentpack/context.md"):
13
+ self._output = output
14
+
15
+ def output_path(self, root: Path) -> Path:
16
+ return root / self._output
17
+
18
+ def render(self, pack: ContextPack) -> str:
19
+ return render_generic(pack)
20
+
21
+ # Delegating install methods — kept for backward compat
22
+ def patch_cursor_rules(self, root: Path) -> str:
23
+ return CursorInstaller().patch_cursor_rules(root)
24
+
25
+ def patch_cursor_mdc(self, root: Path) -> str:
26
+ return CursorInstaller().patch_cursor_mdc(root)
27
+
28
+ def install_auto_repack(self, root: Path) -> dict[str, str]:
29
+ return CursorInstaller().install_auto_repack(root)
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from agentpack.adapters.base import BaseAdapter
6
+ from agentpack.core.models import ContextPack
7
+ from agentpack.renderers.markdown import render_generic
8
+
9
+
10
+ class GenericAdapter(BaseAdapter):
11
+ def __init__(self, output: str = ".agentpack/context.md"):
12
+ self._output = output
13
+
14
+ def output_path(self, root: Path) -> Path:
15
+ return root / self._output
16
+
17
+ def render(self, pack: ContextPack) -> str:
18
+ return render_generic(pack)
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from agentpack.adapters.base import BaseAdapter
6
+ from agentpack.core.models import ContextPack
7
+ from agentpack.renderers.markdown import render_generic
8
+ from agentpack.installers.windsurf import WindsurfInstaller # noqa: F401
9
+
10
+
11
+ class WindsurfAdapter(BaseAdapter):
12
+ def __init__(self, output: str = ".agentpack/context.md"):
13
+ self._output = output
14
+
15
+ def output_path(self, root: Path) -> Path:
16
+ return root / self._output
17
+
18
+ def render(self, pack: ContextPack) -> str:
19
+ return render_generic(pack)
20
+
21
+ # Delegating install methods — kept for backward compat
22
+ def patch_windsurfrules(self, root: Path) -> str:
23
+ return WindsurfInstaller().patch_windsurfrules(root)
24
+
25
+ def install_auto_repack(self, root: Path) -> dict[str, str]:
26
+ return WindsurfInstaller().install_auto_repack(root)
File without changes
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from agentpack.core.models import DependencyGraph, DependencyNode, FileInfo
6
+ from agentpack.analysis.python_imports import extract_imports as py_imports
7
+ from agentpack.analysis.python_imports import resolve_relative_import as py_resolve
8
+ from agentpack.analysis.js_ts_imports import extract_imports as js_imports
9
+ from agentpack.analysis.js_ts_imports import resolve_relative_import as js_resolve
10
+ from agentpack.analysis.go_imports import extract_imports as go_imports
11
+ from agentpack.analysis.rust_imports import extract_imports as rust_imports
12
+ from agentpack.analysis.java_imports import extract_imports as java_imports
13
+
14
+
15
+ def build(
16
+ files: list[FileInfo],
17
+ root: Path,
18
+ summaries: dict | None = None,
19
+ ) -> DependencyGraph:
20
+ """Build an import/imported-by graph over packable files.
21
+
22
+ Args:
23
+ files: Packable (non-ignored, non-binary) FileInfo objects.
24
+ root: Repository root for resolving relative imports.
25
+ summaries: Optional pre-built summary cache; cached imports avoid re-parsing.
26
+
27
+ Returns:
28
+ DependencyGraph with typed DependencyNode entries. Caller fills tests
29
+ via find_related_tests after construction.
30
+ """
31
+ graph = DependencyGraph(
32
+ nodes={fi.path: DependencyNode(path=fi.path) for fi in files}
33
+ )
34
+ path_set = {fi.path for fi in files}
35
+
36
+ for fi in files:
37
+ if summaries and fi.path in summaries:
38
+ cached_imports = summaries[fi.path].get("imports", [])
39
+ if cached_imports:
40
+ graph.nodes[fi.path].imports = cached_imports
41
+ for dep in cached_imports:
42
+ if dep in graph:
43
+ graph.nodes[dep].imported_by.append(fi.path)
44
+ continue
45
+
46
+ raw_imports: list[str] = []
47
+ lang = fi.language
48
+ cached = fi.content
49
+
50
+ if lang == "python":
51
+ raw_imports = py_imports(fi.abs_path, cached)
52
+ elif lang in ("javascript", "typescript"):
53
+ raw_imports = js_imports(fi.abs_path, cached)
54
+ elif lang == "go":
55
+ raw_imports = go_imports(fi.abs_path, cached)
56
+ elif lang == "rust":
57
+ raw_imports = rust_imports(fi.abs_path, cached)
58
+ elif lang in ("java", "kotlin"):
59
+ raw_imports = java_imports(fi.abs_path, cached)
60
+
61
+ resolved: list[str] = []
62
+ for imp in raw_imports:
63
+ if imp.startswith("."):
64
+ if lang == "python":
65
+ r = py_resolve(fi.path, imp, root)
66
+ elif lang in ("javascript", "typescript"):
67
+ r = js_resolve(fi.path, imp, root)
68
+ else:
69
+ r = None
70
+ if r and r in path_set:
71
+ resolved.append(r)
72
+ else:
73
+ resolved.append(imp)
74
+
75
+ graph.nodes[fi.path].imports = resolved
76
+ for dep in resolved:
77
+ if dep in graph:
78
+ graph.nodes[dep].imported_by.append(fi.path)
79
+
80
+ return graph
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from pathlib import Path
5
+
6
+ _SINGLE = re.compile(r'^import\s+"([^"]+)"', re.MULTILINE)
7
+ _BLOCK_START = re.compile(r"^import\s+\(", re.MULTILINE)
8
+ _BLOCK_ENTRY = re.compile(r'^\s+(?:\w+\s+)?"([^"]+)"')
9
+
10
+
11
+ def extract_imports(path: Path, text: str | None = None) -> list[str]:
12
+ if text is None:
13
+ try:
14
+ text = path.read_text(errors="replace")
15
+ except OSError:
16
+ return []
17
+
18
+ imports: list[str] = []
19
+ imports.extend(m.group(1) for m in _SINGLE.finditer(text))
20
+
21
+ for block_m in _BLOCK_START.finditer(text):
22
+ rest = text[block_m.end():]
23
+ end = rest.find(")")
24
+ if end == -1:
25
+ continue
26
+ block = rest[:end]
27
+ for line in block.splitlines():
28
+ m = _BLOCK_ENTRY.match(line)
29
+ if m:
30
+ imports.append(m.group(1))
31
+
32
+ return imports
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from pathlib import Path
5
+
6
+ _IMPORT = re.compile(r"^import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;", re.MULTILINE)
7
+ _KOTLIN_IMPORT = re.compile(r"^import\s+([\w.]+(?:\.\*)?)", re.MULTILINE)
8
+
9
+
10
+ def extract_imports(path: Path, text: str | None = None) -> list[str]:
11
+ if text is None:
12
+ try:
13
+ text = path.read_text(errors="replace")
14
+ except OSError:
15
+ return []
16
+
17
+ suffix = path.suffix.lower()
18
+ pattern = _KOTLIN_IMPORT if suffix == ".kt" else _IMPORT
19
+ return [m.group(1) for m in pattern.finditer(text)]
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from pathlib import Path
5
+
6
+ _IMPORT_PATTERNS = [
7
+ re.compile(r'import\s+.*?\s+from\s+["\']([^"\']+)["\']'),
8
+ re.compile(r'import\s+["\']([^"\']+)["\']'),
9
+ re.compile(r'require\s*\(\s*["\']([^"\']+)["\']\s*\)'),
10
+ re.compile(r'export\s+.*?\s+from\s+["\']([^"\']+)["\']'),
11
+ ]
12
+
13
+ _RELATIVE_EXTS = (".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs")
14
+
15
+
16
+ def extract_imports(path: Path, text: str | None = None) -> list[str]:
17
+ if text is None:
18
+ try:
19
+ text = path.read_text(errors="replace")
20
+ except OSError:
21
+ return []
22
+
23
+ imports: list[str] = []
24
+ for pattern in _IMPORT_PATTERNS:
25
+ for m in pattern.finditer(text):
26
+ imports.append(m.group(1))
27
+ return imports
28
+
29
+
30
+ def resolve_relative_import(importer: str, import_str: str, root: Path) -> str | None:
31
+ if not import_str.startswith("."):
32
+ return None
33
+
34
+ base = (root / importer).parent
35
+ candidate = (base / import_str).resolve()
36
+
37
+ for ext in _RELATIVE_EXTS:
38
+ p = candidate.with_suffix(ext)
39
+ if p.exists():
40
+ try:
41
+ return str(p.relative_to(root))
42
+ except ValueError:
43
+ pass
44
+
45
+ for ext in _RELATIVE_EXTS:
46
+ p = candidate / f"index{ext}"
47
+ if p.exists():
48
+ try:
49
+ return str(p.relative_to(root))
50
+ except ValueError:
51
+ pass
52
+
53
+ return None
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from pathlib import Path
5
+
6
+
7
+ def extract_imports(path: Path, text: str | None = None) -> list[str]:
8
+ try:
9
+ source = text if text is not None else path.read_text(errors="replace")
10
+ tree = ast.parse(source)
11
+ except SyntaxError:
12
+ return []
13
+
14
+ imports: list[str] = []
15
+ for node in ast.walk(tree):
16
+ if isinstance(node, ast.Import):
17
+ for alias in node.names:
18
+ imports.append(alias.name)
19
+ elif isinstance(node, ast.ImportFrom):
20
+ module = node.module or ""
21
+ level = node.level or 0
22
+ prefix = "." * level
23
+ imports.append(f"{prefix}{module}" if module else prefix)
24
+ return imports
25
+
26
+
27
+ def resolve_relative_import(importer: str, import_str: str, root: Path) -> str | None:
28
+ """Resolve a relative Python import to a file path relative to root."""
29
+ if not import_str.startswith("."):
30
+ return None
31
+
32
+ dots = len(import_str) - len(import_str.lstrip("."))
33
+ module = import_str[dots:].replace(".", "/")
34
+
35
+ base = Path(importer).parent
36
+ for _ in range(dots - 1):
37
+ base = base.parent
38
+
39
+ candidate = base / module
40
+ for suffix in (".py", "/__init__.py"):
41
+ full = root / (str(candidate) + suffix)
42
+ if full.exists():
43
+ return str((candidate).with_suffix(".py") if suffix == ".py" else candidate / "__init__.py")
44
+
45
+ return None