tostr 0.1.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.
- tostr-0.1.0/LICENSE +21 -0
- tostr-0.1.0/PKG-INFO +149 -0
- tostr-0.1.0/README.md +128 -0
- tostr-0.1.0/pyproject.toml +32 -0
- tostr-0.1.0/setup.cfg +4 -0
- tostr-0.1.0/src/tostr/__init__.py +0 -0
- tostr-0.1.0/src/tostr/cli.py +329 -0
- tostr-0.1.0/src/tostr/commands.py +197 -0
- tostr-0.1.0/src/tostr/core/__init__.py +6 -0
- tostr-0.1.0/src/tostr/core/builders.py +156 -0
- tostr-0.1.0/src/tostr/core/context/__init__.py +1 -0
- tostr-0.1.0/src/tostr/core/context/config.py +72 -0
- tostr-0.1.0/src/tostr/core/db.py +81 -0
- tostr-0.1.0/src/tostr/core/models.py +674 -0
- tostr-0.1.0/src/tostr/core/parser.py +98 -0
- tostr-0.1.0/src/tostr/core/providers.py +95 -0
- tostr-0.1.0/src/tostr/core/registry.py +387 -0
- tostr-0.1.0/src/tostr/core/serializer.py +174 -0
- tostr-0.1.0/src/tostr/core/utils/logger.py +62 -0
- tostr-0.1.0/src/tostr/exceptions.py +22 -0
- tostr-0.1.0/src/tostr/languages/__init__.py +0 -0
- tostr-0.1.0/src/tostr/languages/java/__init__.py +1 -0
- tostr-0.1.0/src/tostr/languages/java/builders.py +335 -0
- tostr-0.1.0/src/tostr/languages/java/language.py +4 -0
- tostr-0.1.0/src/tostr/languages/java/queries.py +19 -0
- tostr-0.1.0/src/tostr/llm/__init__.py +3 -0
- tostr-0.1.0/src/tostr/llm/base.py +69 -0
- tostr-0.1.0/src/tostr/llm/gemini.py +30 -0
- tostr-0.1.0/src/tostr/llm/prompts.py +38 -0
- tostr-0.1.0/src/tostr/server.py +251 -0
- tostr-0.1.0/src/tostr.egg-info/PKG-INFO +149 -0
- tostr-0.1.0/src/tostr.egg-info/SOURCES.txt +34 -0
- tostr-0.1.0/src/tostr.egg-info/dependency_links.txt +1 -0
- tostr-0.1.0/src/tostr.egg-info/entry_points.txt +2 -0
- tostr-0.1.0/src/tostr.egg-info/requires.txt +11 -0
- tostr-0.1.0/src/tostr.egg-info/top_level.txt +1 -0
tostr-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Avery Brown
|
|
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.
|
tostr-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tostr
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Token-Optimized Syntax Tree String IR Generator
|
|
5
|
+
Author: Avery Brown
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: tree-sitter>=0.21.0
|
|
10
|
+
Requires-Dist: tree-sitter-java
|
|
11
|
+
Requires-Dist: tree-sitter-c-sharp
|
|
12
|
+
Requires-Dist: tree-sitter-python
|
|
13
|
+
Requires-Dist: google-genai
|
|
14
|
+
Requires-Dist: pydantic
|
|
15
|
+
Requires-Dist: typer
|
|
16
|
+
Requires-Dist: watchfiles
|
|
17
|
+
Requires-Dist: loguru
|
|
18
|
+
Requires-Dist: fastmcp
|
|
19
|
+
Requires-Dist: pathspec
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
<p align="center">
|
|
23
|
+
<a href="https://toastedtools.com/"><img src="./logo.png" alt="Tostr Logo" width="816"></a>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<h1 align="center">
|
|
27
|
+
Pre-computing Agentic AI Code Context
|
|
28
|
+
</h1>
|
|
29
|
+
|
|
30
|
+
<!-- usage gif goes here -->
|
|
31
|
+
|
|
32
|
+
<p align="center">
|
|
33
|
+
Tostr is a CLI and MCP agent context engine which greatly reduces token costs and context bloat for agentic LLM coding assistants by pre-computing an llm-described AST in the .tost format
|
|
34
|
+
</p>
|
|
35
|
+
|
|
36
|
+
# Features
|
|
37
|
+
### Pre-computed Abstract Syntax Tree
|
|
38
|
+
Tostr scrapes your project on initialization, building a comprehensive Abstract Syntax Tree IR (Intermediate Representation) of the entire OOP code structure and stores it in a local SQLite database.
|
|
39
|
+
|
|
40
|
+
### Semantic Dependency Graph Resolution
|
|
41
|
+
Tostr resolves dependencies between structures in your code, building a dependency graph to allow agents to traverse inbound or outbound method calls efficiently.
|
|
42
|
+
|
|
43
|
+
### MCP and CLI access
|
|
44
|
+
Tostr has both a CLI and MCP interface, allowing llms to boot up the mcp server for larger development sessions, while allowing agents or human developers to utilize the CLI for individual actions or quick, manual AST traversals.
|
|
45
|
+
|
|
46
|
+
### Automatic Incremental Change Diffs
|
|
47
|
+
While the MCP server is running, Tostr identifies the subtree of the AST which was updated on file save, add, or delete, then re-scrapes and re-describes exactly the section that was updated, ensuring that the AST is instantly up-to-date during development.
|
|
48
|
+
|
|
49
|
+
### Lightweight SQLite Cache
|
|
50
|
+
The AST IR and Dependency Graph is cached to an on-drive SQLite .db file to vastly increase efficiency of agent AST traversal requests, as well as allow the AST to be directly queried via sql commands.
|
|
51
|
+
|
|
52
|
+
# Quick Start
|
|
53
|
+
<!-- downnload and install TBD -->
|
|
54
|
+
|
|
55
|
+
## Initializing Tostr
|
|
56
|
+
Before being able to use Tostr, the repository must be initialized using the CLI or MCP.
|
|
57
|
+
|
|
58
|
+
To manually initialize the repository, cd to the root of the project and run:
|
|
59
|
+
```
|
|
60
|
+
% tostr init . --ignore 'default'
|
|
61
|
+
```
|
|
62
|
+
<!--
|
|
63
|
+
This creates the .Tostr directory and initializes the default *.toastignore* to exclude environment files, node_modules, build artifacts, and other files which are not needed in the project AST
|
|
64
|
+
|
|
65
|
+
In the */.tostr* directory, you will also find the *config.toml* file to configure the other adjustable parameters (The full list of configuration parameters can be found ***Here***)
|
|
66
|
+
-->
|
|
67
|
+
## Parsing the project
|
|
68
|
+
Once the project itself is initialized and configured, the AST cache has to be initialized. To do this manually, cd to the project root (where you initialized the .Tostr files) and run:
|
|
69
|
+
```
|
|
70
|
+
% tostr parse .
|
|
71
|
+
```
|
|
72
|
+
This will take anywhere from a few seconds to a few minutes depending on the size of your repository, as the CLI parses the repository using tree-sitter, then passes the AST concurrently to your configured LLM provider for describing and embedding. Projects with particularly large individual classes will take longer to parse, since the description generation is blocked by class.
|
|
73
|
+
> The time it takes to parse during this step is one time per project, as the incremental diffing allows further parses to only update the cache invalidations
|
|
74
|
+
|
|
75
|
+
## Using the CLI
|
|
76
|
+
Now that the project is initialized and parsed, Tostr is ready to go!
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
### Project Skeleton
|
|
80
|
+
To test it, navigate to the project root and run:
|
|
81
|
+
```
|
|
82
|
+
% tostr skeleton . --depth 1
|
|
83
|
+
```
|
|
84
|
+
You should see Tostr print the AST skeleton of your root and its direct children directories to your console.
|
|
85
|
+
> The 'depth' parameter can be adjusted to determine how many layers into the file tree should be skeletonized and printed (default is infinite, or the whole subtree of the path provided)
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
% tostr skeleton . --depth 1
|
|
89
|
+
|
|
90
|
+
/src/project/foo.py
|
|
91
|
+
C-1234 | class Foo(Bar)
|
|
92
|
+
// Description of the Foo class, outlining usage and purpose
|
|
93
|
+
rather than syntax
|
|
94
|
+
|
|
95
|
+
/src/project/child_dir/fizz.py
|
|
96
|
+
C-1235 | class Fizz
|
|
97
|
+
// Description of the Fizz class...
|
|
98
|
+
C-1236 | class Buzz(Fizz)
|
|
99
|
+
// Description of the Buzz class...
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Inspecting Structs
|
|
104
|
+
|
|
105
|
+
Each of the structs (files, classes, methods) can be inspected further to see more details about them:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
% tostr inspect 'C-1234' --pretty
|
|
109
|
+
|
|
110
|
+
/src/project/foo.py
|
|
111
|
+
C-1234 | class Foo(Bar)
|
|
112
|
+
// Description of the Foo class, outlining usage and purpose
|
|
113
|
+
rather than syntax
|
|
114
|
+
fields:
|
|
115
|
+
int num1, num2; Fizz field3; Buzz field4, field5
|
|
116
|
+
methods:
|
|
117
|
+
M-12345 @L10-12 | def async foobar(num1: int = 0) -> int
|
|
118
|
+
// Description of the foobar method...
|
|
119
|
+
< child.Fizz#outbound_dependency1(), ~M-12346|#foo(num: int),
|
|
120
|
+
~M-12347|#bar(num:int)
|
|
121
|
+
|
|
122
|
+
M-12346 @L22-24 | def foo(num: int) -> int
|
|
123
|
+
// Description of the foo method...
|
|
124
|
+
|
|
125
|
+
M-12347 @L27-30 | def bar(num: int) -> int
|
|
126
|
+
// Description of the bar method...
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
You can also use flags to expand the detail of the output:
|
|
130
|
+
* `-v` / `--verbose`: Increases the verbosity of inspect commands to include more information, such as inbound dependencies (`>`) and impact scores.
|
|
131
|
+
* `-b` / `--body`: Attaches the body source code of the root struct being inspected to the bottom of the output.
|
|
132
|
+
* `-r` / `--raw`: Disables pretty printing, or the indentation and line-wrapping configured in `.Tostr/config.toml`. Pretty printing is active by default for CLI commands but inactive for MCP commands.
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
% tostr inspect 'M-12345' -v -b
|
|
136
|
+
/src/project/foo.py
|
|
137
|
+
C-1234 | Project.Foo
|
|
138
|
+
M-12345 @L10-20 | def async foobar(num1: int = 0) -> int
|
|
139
|
+
// Description of the foobar method non-truncated
|
|
140
|
+
< child.Fizz#outbound_dependency1(), ~M-12346|#foo(num: int),
|
|
141
|
+
~M-12347|#bar(num:int)
|
|
142
|
+
> child.Fizz#inbound_dependency1(num1: int)
|
|
143
|
+
# impact score: 5
|
|
144
|
+
|
|
145
|
+
'''
|
|
146
|
+
self.field3.inbound_dependency1(num1)
|
|
147
|
+
return num1 + self.foo(num1) + self.bar(num1)
|
|
148
|
+
'''
|
|
149
|
+
```
|
tostr-0.1.0/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://toastedtools.com/"><img src="./logo.png" alt="Tostr Logo" width="816"></a>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">
|
|
6
|
+
Pre-computing Agentic AI Code Context
|
|
7
|
+
</h1>
|
|
8
|
+
|
|
9
|
+
<!-- usage gif goes here -->
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
Tostr is a CLI and MCP agent context engine which greatly reduces token costs and context bloat for agentic LLM coding assistants by pre-computing an llm-described AST in the .tost format
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
# Features
|
|
16
|
+
### Pre-computed Abstract Syntax Tree
|
|
17
|
+
Tostr scrapes your project on initialization, building a comprehensive Abstract Syntax Tree IR (Intermediate Representation) of the entire OOP code structure and stores it in a local SQLite database.
|
|
18
|
+
|
|
19
|
+
### Semantic Dependency Graph Resolution
|
|
20
|
+
Tostr resolves dependencies between structures in your code, building a dependency graph to allow agents to traverse inbound or outbound method calls efficiently.
|
|
21
|
+
|
|
22
|
+
### MCP and CLI access
|
|
23
|
+
Tostr has both a CLI and MCP interface, allowing llms to boot up the mcp server for larger development sessions, while allowing agents or human developers to utilize the CLI for individual actions or quick, manual AST traversals.
|
|
24
|
+
|
|
25
|
+
### Automatic Incremental Change Diffs
|
|
26
|
+
While the MCP server is running, Tostr identifies the subtree of the AST which was updated on file save, add, or delete, then re-scrapes and re-describes exactly the section that was updated, ensuring that the AST is instantly up-to-date during development.
|
|
27
|
+
|
|
28
|
+
### Lightweight SQLite Cache
|
|
29
|
+
The AST IR and Dependency Graph is cached to an on-drive SQLite .db file to vastly increase efficiency of agent AST traversal requests, as well as allow the AST to be directly queried via sql commands.
|
|
30
|
+
|
|
31
|
+
# Quick Start
|
|
32
|
+
<!-- downnload and install TBD -->
|
|
33
|
+
|
|
34
|
+
## Initializing Tostr
|
|
35
|
+
Before being able to use Tostr, the repository must be initialized using the CLI or MCP.
|
|
36
|
+
|
|
37
|
+
To manually initialize the repository, cd to the root of the project and run:
|
|
38
|
+
```
|
|
39
|
+
% tostr init . --ignore 'default'
|
|
40
|
+
```
|
|
41
|
+
<!--
|
|
42
|
+
This creates the .Tostr directory and initializes the default *.toastignore* to exclude environment files, node_modules, build artifacts, and other files which are not needed in the project AST
|
|
43
|
+
|
|
44
|
+
In the */.tostr* directory, you will also find the *config.toml* file to configure the other adjustable parameters (The full list of configuration parameters can be found ***Here***)
|
|
45
|
+
-->
|
|
46
|
+
## Parsing the project
|
|
47
|
+
Once the project itself is initialized and configured, the AST cache has to be initialized. To do this manually, cd to the project root (where you initialized the .Tostr files) and run:
|
|
48
|
+
```
|
|
49
|
+
% tostr parse .
|
|
50
|
+
```
|
|
51
|
+
This will take anywhere from a few seconds to a few minutes depending on the size of your repository, as the CLI parses the repository using tree-sitter, then passes the AST concurrently to your configured LLM provider for describing and embedding. Projects with particularly large individual classes will take longer to parse, since the description generation is blocked by class.
|
|
52
|
+
> The time it takes to parse during this step is one time per project, as the incremental diffing allows further parses to only update the cache invalidations
|
|
53
|
+
|
|
54
|
+
## Using the CLI
|
|
55
|
+
Now that the project is initialized and parsed, Tostr is ready to go!
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
### Project Skeleton
|
|
59
|
+
To test it, navigate to the project root and run:
|
|
60
|
+
```
|
|
61
|
+
% tostr skeleton . --depth 1
|
|
62
|
+
```
|
|
63
|
+
You should see Tostr print the AST skeleton of your root and its direct children directories to your console.
|
|
64
|
+
> The 'depth' parameter can be adjusted to determine how many layers into the file tree should be skeletonized and printed (default is infinite, or the whole subtree of the path provided)
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
% tostr skeleton . --depth 1
|
|
68
|
+
|
|
69
|
+
/src/project/foo.py
|
|
70
|
+
C-1234 | class Foo(Bar)
|
|
71
|
+
// Description of the Foo class, outlining usage and purpose
|
|
72
|
+
rather than syntax
|
|
73
|
+
|
|
74
|
+
/src/project/child_dir/fizz.py
|
|
75
|
+
C-1235 | class Fizz
|
|
76
|
+
// Description of the Fizz class...
|
|
77
|
+
C-1236 | class Buzz(Fizz)
|
|
78
|
+
// Description of the Buzz class...
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Inspecting Structs
|
|
83
|
+
|
|
84
|
+
Each of the structs (files, classes, methods) can be inspected further to see more details about them:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
% tostr inspect 'C-1234' --pretty
|
|
88
|
+
|
|
89
|
+
/src/project/foo.py
|
|
90
|
+
C-1234 | class Foo(Bar)
|
|
91
|
+
// Description of the Foo class, outlining usage and purpose
|
|
92
|
+
rather than syntax
|
|
93
|
+
fields:
|
|
94
|
+
int num1, num2; Fizz field3; Buzz field4, field5
|
|
95
|
+
methods:
|
|
96
|
+
M-12345 @L10-12 | def async foobar(num1: int = 0) -> int
|
|
97
|
+
// Description of the foobar method...
|
|
98
|
+
< child.Fizz#outbound_dependency1(), ~M-12346|#foo(num: int),
|
|
99
|
+
~M-12347|#bar(num:int)
|
|
100
|
+
|
|
101
|
+
M-12346 @L22-24 | def foo(num: int) -> int
|
|
102
|
+
// Description of the foo method...
|
|
103
|
+
|
|
104
|
+
M-12347 @L27-30 | def bar(num: int) -> int
|
|
105
|
+
// Description of the bar method...
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
You can also use flags to expand the detail of the output:
|
|
109
|
+
* `-v` / `--verbose`: Increases the verbosity of inspect commands to include more information, such as inbound dependencies (`>`) and impact scores.
|
|
110
|
+
* `-b` / `--body`: Attaches the body source code of the root struct being inspected to the bottom of the output.
|
|
111
|
+
* `-r` / `--raw`: Disables pretty printing, or the indentation and line-wrapping configured in `.Tostr/config.toml`. Pretty printing is active by default for CLI commands but inactive for MCP commands.
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
% tostr inspect 'M-12345' -v -b
|
|
115
|
+
/src/project/foo.py
|
|
116
|
+
C-1234 | Project.Foo
|
|
117
|
+
M-12345 @L10-20 | def async foobar(num1: int = 0) -> int
|
|
118
|
+
// Description of the foobar method non-truncated
|
|
119
|
+
< child.Fizz#outbound_dependency1(), ~M-12346|#foo(num: int),
|
|
120
|
+
~M-12347|#bar(num:int)
|
|
121
|
+
> child.Fizz#inbound_dependency1(num1: int)
|
|
122
|
+
# impact score: 5
|
|
123
|
+
|
|
124
|
+
'''
|
|
125
|
+
self.field3.inbound_dependency1(num1)
|
|
126
|
+
return num1 + self.foo(num1) + self.bar(num1)
|
|
127
|
+
'''
|
|
128
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tostr"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Avery Brown" }
|
|
10
|
+
]
|
|
11
|
+
description = "Token-Optimized Syntax Tree String IR Generator"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"tree-sitter>=0.21.0",
|
|
16
|
+
"tree-sitter-java",
|
|
17
|
+
"tree-sitter-c-sharp",
|
|
18
|
+
"tree-sitter-python",
|
|
19
|
+
"google-genai",
|
|
20
|
+
"pydantic",
|
|
21
|
+
"typer",
|
|
22
|
+
"watchfiles",
|
|
23
|
+
"loguru",
|
|
24
|
+
"fastmcp",
|
|
25
|
+
"pathspec"
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
tostr = "tostr.cli:app"
|
|
30
|
+
|
|
31
|
+
[tool.pytest.ini_options]
|
|
32
|
+
pythonpath = ["src"]
|
tostr-0.1.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import time
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import typer
|
|
5
|
+
from typing import Annotated, List
|
|
6
|
+
from loguru import logger
|
|
7
|
+
from tostr.exceptions import TostrError
|
|
8
|
+
|
|
9
|
+
from tostr.commands import (
|
|
10
|
+
init_async,
|
|
11
|
+
inspect_async,
|
|
12
|
+
skeleton_async,
|
|
13
|
+
watch_async,
|
|
14
|
+
clean_db,
|
|
15
|
+
resolve_uid_to_id
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from tostr.server import mcp
|
|
19
|
+
|
|
20
|
+
from tostr.core.utils.logger import configure_cli_logging
|
|
21
|
+
|
|
22
|
+
import multiprocessing
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Initialize the Typer app
|
|
26
|
+
app = typer.Typer(
|
|
27
|
+
name="tostr",
|
|
28
|
+
help="AST scraper for LLM RAG context generation.",
|
|
29
|
+
add_completion=False # Optional: Turns off the auto-generated completion install command for cleaner help menus
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def _run_watcher_thread(target_path: Path):
|
|
33
|
+
"""
|
|
34
|
+
Sets up an isolated async environment for the background thread.
|
|
35
|
+
"""
|
|
36
|
+
loop = asyncio.new_event_loop()
|
|
37
|
+
asyncio.set_event_loop(loop)
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
logger.info(f"Background watcher started on {target_path}")
|
|
41
|
+
loop.run_until_complete(watch_async(target_path))
|
|
42
|
+
except Exception as e:
|
|
43
|
+
logger.exception(f"Fatal error in background watcher: {e}")
|
|
44
|
+
finally:
|
|
45
|
+
loop.close()
|
|
46
|
+
logger.info("Background watcher shut down.")
|
|
47
|
+
|
|
48
|
+
@app.command("start-mcp")
|
|
49
|
+
def start_mcp():
|
|
50
|
+
"""Start the bare MCP server. Awaits agent initialization."""
|
|
51
|
+
mcp.run()
|
|
52
|
+
|
|
53
|
+
@app.command()
|
|
54
|
+
def watch(
|
|
55
|
+
path: Path = typer.Argument(
|
|
56
|
+
".",
|
|
57
|
+
help="Path to the project directory to scan",
|
|
58
|
+
exists=True, # Typer automatically checks if the path exists
|
|
59
|
+
file_okay=False, # Typer blocks files, only allowing directories
|
|
60
|
+
dir_okay=True,
|
|
61
|
+
resolve_path=True # Converts relative paths to absolute paths automatically
|
|
62
|
+
),
|
|
63
|
+
debug: Annotated[
|
|
64
|
+
bool,
|
|
65
|
+
typer.Option(
|
|
66
|
+
"--debug/--no-debug",
|
|
67
|
+
"-d/-nd",
|
|
68
|
+
help="Enable debug logging"
|
|
69
|
+
)
|
|
70
|
+
] = False
|
|
71
|
+
):
|
|
72
|
+
"""Watch for changes to files and update the SQLite database."""
|
|
73
|
+
configure_cli_logging(debug)
|
|
74
|
+
try:
|
|
75
|
+
asyncio.run(watch_async(path))
|
|
76
|
+
except TostrError as e:
|
|
77
|
+
typer.secho(f"❌ Error: {e}", fg="red", err=True)
|
|
78
|
+
raise typer.Exit(code=1)
|
|
79
|
+
|
|
80
|
+
@app.command()
|
|
81
|
+
def clean(
|
|
82
|
+
path: Path = typer.Argument(
|
|
83
|
+
".",
|
|
84
|
+
help="Path to the project directory to scan",
|
|
85
|
+
exists=True, # Typer automatically checks if the path exists
|
|
86
|
+
file_okay=False, # Typer blocks files, only allowing directories
|
|
87
|
+
dir_okay=True,
|
|
88
|
+
resolve_path=True # Converts relative paths to absolute paths automatically
|
|
89
|
+
),
|
|
90
|
+
debug: Annotated[
|
|
91
|
+
bool,
|
|
92
|
+
typer.Option(
|
|
93
|
+
"--debug/--no-debug",
|
|
94
|
+
"-d/-nd",
|
|
95
|
+
help="Enable debug logging"
|
|
96
|
+
)
|
|
97
|
+
] = False
|
|
98
|
+
):
|
|
99
|
+
"""Clean the SQLite database."""
|
|
100
|
+
configure_cli_logging(debug)
|
|
101
|
+
try:
|
|
102
|
+
clean_db(path)
|
|
103
|
+
except TostrError as e:
|
|
104
|
+
typer.secho(f"❌ Error: {e}", fg="red", err=True)
|
|
105
|
+
raise typer.Exit(code=1)
|
|
106
|
+
|
|
107
|
+
@app.command()
|
|
108
|
+
def init(
|
|
109
|
+
path: Path = typer.Argument(
|
|
110
|
+
".",
|
|
111
|
+
help="Path to the project directory to scan",
|
|
112
|
+
exists=True, # Typer automatically checks if the path exists
|
|
113
|
+
file_okay=False, # Typer blocks files, only allowing directories
|
|
114
|
+
dir_okay=True,
|
|
115
|
+
resolve_path=True # Converts relative paths to absolute paths automatically
|
|
116
|
+
),
|
|
117
|
+
use_cache: Annotated[
|
|
118
|
+
bool,
|
|
119
|
+
typer.Option(
|
|
120
|
+
"--use-cache/--no-cache",
|
|
121
|
+
help="Load cache if it exists"
|
|
122
|
+
)
|
|
123
|
+
] = True,
|
|
124
|
+
ignore: Annotated[
|
|
125
|
+
str,
|
|
126
|
+
typer.Option(
|
|
127
|
+
"--ignore",
|
|
128
|
+
"-i",
|
|
129
|
+
help="Add a default ignore template to the project folder (e.g., 'java', 'default')"
|
|
130
|
+
)
|
|
131
|
+
] = None,
|
|
132
|
+
debug: Annotated[
|
|
133
|
+
bool,
|
|
134
|
+
typer.Option(
|
|
135
|
+
"--debug/--no-debug",
|
|
136
|
+
"-d/-nd",
|
|
137
|
+
help="Enable debug logging"
|
|
138
|
+
)
|
|
139
|
+
] = False
|
|
140
|
+
):
|
|
141
|
+
"""Parse files and setup SQLite database."""
|
|
142
|
+
configure_cli_logging(debug)
|
|
143
|
+
start_time = time.perf_counter()
|
|
144
|
+
try:
|
|
145
|
+
asyncio.run(init_async(path, use_cache, ignore))
|
|
146
|
+
except TostrError as e:
|
|
147
|
+
typer.secho(f"❌ Error: {e}", fg="red", err=True)
|
|
148
|
+
raise typer.Exit(code=1)
|
|
149
|
+
|
|
150
|
+
end_time = time.perf_counter()
|
|
151
|
+
elapsed_time = end_time - start_time
|
|
152
|
+
logger.debug(f"Finished in {elapsed_time:.4f} seconds.")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@app.command()
|
|
156
|
+
def resolve(
|
|
157
|
+
uid: Annotated[
|
|
158
|
+
str,
|
|
159
|
+
typer.Argument(help="The UID to resolve to an ID")
|
|
160
|
+
],
|
|
161
|
+
path: Path = typer.Argument(
|
|
162
|
+
".",
|
|
163
|
+
help="Path to the project directory to scan",
|
|
164
|
+
exists=True,
|
|
165
|
+
file_okay=False,
|
|
166
|
+
dir_okay=True,
|
|
167
|
+
resolve_path=True
|
|
168
|
+
),
|
|
169
|
+
debug: Annotated[
|
|
170
|
+
bool,
|
|
171
|
+
typer.Option(
|
|
172
|
+
"--debug/--no-debug",
|
|
173
|
+
"-d/-nd",
|
|
174
|
+
help="Enable debug logging"
|
|
175
|
+
)
|
|
176
|
+
] = False
|
|
177
|
+
):
|
|
178
|
+
"""Resolve a UID to its corresponding struct ID."""
|
|
179
|
+
configure_cli_logging(debug)
|
|
180
|
+
try:
|
|
181
|
+
result = resolve_uid_to_id(uid, path)
|
|
182
|
+
if result:
|
|
183
|
+
print(result)
|
|
184
|
+
else:
|
|
185
|
+
typer.secho(f"❌ Error: No struct found with UID '{uid}'", fg="red", err=True)
|
|
186
|
+
raise typer.Exit(code=1)
|
|
187
|
+
except TostrError as e:
|
|
188
|
+
typer.secho(f"❌ Error: {e}", fg="red", err=True)
|
|
189
|
+
raise typer.Exit(code=1)
|
|
190
|
+
|
|
191
|
+
@app.command()
|
|
192
|
+
def inspect(
|
|
193
|
+
ids: Annotated[
|
|
194
|
+
List[str],
|
|
195
|
+
typer.Argument(help="List of struct IDs or UIDs to inspect")
|
|
196
|
+
],
|
|
197
|
+
path: Path = typer.Argument(
|
|
198
|
+
".",
|
|
199
|
+
help="Path to the project directory to scan",
|
|
200
|
+
exists=True, # Typer automatically checks if the path exists
|
|
201
|
+
file_okay=False, # Typer blocks files, only allowing directories
|
|
202
|
+
dir_okay=True,
|
|
203
|
+
resolve_path=True # Converts relative paths to absolute paths automatically
|
|
204
|
+
),
|
|
205
|
+
include_body: Annotated[
|
|
206
|
+
bool,
|
|
207
|
+
typer.Option(
|
|
208
|
+
"--body/--no-body",
|
|
209
|
+
help="Include code body in output"
|
|
210
|
+
)
|
|
211
|
+
] = False,
|
|
212
|
+
pretty: Annotated[
|
|
213
|
+
bool,
|
|
214
|
+
typer.Option(
|
|
215
|
+
"--pretty/--raw",
|
|
216
|
+
help="Pretty format output with line wrapping and indentation (disable for raw output)"
|
|
217
|
+
)
|
|
218
|
+
] = True,
|
|
219
|
+
debug: Annotated[
|
|
220
|
+
bool,
|
|
221
|
+
typer.Option(
|
|
222
|
+
"--debug/--no-debug",
|
|
223
|
+
"-d/-nd",
|
|
224
|
+
help="Enable debug logging"
|
|
225
|
+
)
|
|
226
|
+
] = False,
|
|
227
|
+
max_lines: Annotated[
|
|
228
|
+
int,
|
|
229
|
+
typer.Option(
|
|
230
|
+
"--max-lines",
|
|
231
|
+
"-m",
|
|
232
|
+
help="Maximum number of lines to include in the output (default: 500)"
|
|
233
|
+
)
|
|
234
|
+
] = 500
|
|
235
|
+
):
|
|
236
|
+
"""Output the AST details for specific struct IDs or UIDs."""
|
|
237
|
+
configure_cli_logging(debug)
|
|
238
|
+
|
|
239
|
+
start_time = time.perf_counter()
|
|
240
|
+
try:
|
|
241
|
+
result = asyncio.run(inspect_async(ids, path, include_body=include_body, pretty=pretty))
|
|
242
|
+
lines = result.splitlines()
|
|
243
|
+
if len(lines) > max_lines:
|
|
244
|
+
result = "\n".join(lines[:max_lines]) + "\n...[OUTPUT TRUNCATED AT 500 LINES] - Use a higher '--max-lines <N>' to see more."
|
|
245
|
+
print(result)
|
|
246
|
+
except TostrError as e:
|
|
247
|
+
typer.secho(f"❌ Error: {e}", fg="red", err=True)
|
|
248
|
+
raise typer.Exit(code=1)
|
|
249
|
+
|
|
250
|
+
end_time = time.perf_counter()
|
|
251
|
+
elapsed_time = end_time - start_time
|
|
252
|
+
logger.debug(f"Finished in {elapsed_time:.4f} seconds.")
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@app.command()
|
|
256
|
+
def skeleton(
|
|
257
|
+
subpath: Annotated[
|
|
258
|
+
str,
|
|
259
|
+
typer.Argument(help="File or directory path relative to the project root to generate a skeleton for")
|
|
260
|
+
] = ".",
|
|
261
|
+
path: Path = typer.Argument(
|
|
262
|
+
".",
|
|
263
|
+
help="Path to the project directory to scan",
|
|
264
|
+
exists=True,
|
|
265
|
+
file_okay=False,
|
|
266
|
+
dir_okay=True,
|
|
267
|
+
resolve_path=True
|
|
268
|
+
),
|
|
269
|
+
pretty: Annotated[
|
|
270
|
+
bool,
|
|
271
|
+
typer.Option(
|
|
272
|
+
"--pretty/--raw",
|
|
273
|
+
help="Pretty format output with line wrapping and indentation (disable for raw output)"
|
|
274
|
+
)
|
|
275
|
+
] = True,
|
|
276
|
+
debug: Annotated[
|
|
277
|
+
bool,
|
|
278
|
+
typer.Option(
|
|
279
|
+
"--debug/--no-debug",
|
|
280
|
+
"-d/-nd",
|
|
281
|
+
help="Enable debug logging"
|
|
282
|
+
)
|
|
283
|
+
] = False,
|
|
284
|
+
depth: Annotated[
|
|
285
|
+
int,
|
|
286
|
+
typer.Option(
|
|
287
|
+
"--depth",
|
|
288
|
+
"-d",
|
|
289
|
+
help="Depth to traverse for skeleton generation (default: 7)"
|
|
290
|
+
)
|
|
291
|
+
] = 7,
|
|
292
|
+
files_only: Annotated[
|
|
293
|
+
bool,
|
|
294
|
+
typer.Option(
|
|
295
|
+
"--files-only",
|
|
296
|
+
"-f",
|
|
297
|
+
help="Only generate skeleton for files (default: False)"
|
|
298
|
+
)
|
|
299
|
+
] = False,
|
|
300
|
+
max_lines: Annotated[
|
|
301
|
+
int,
|
|
302
|
+
typer.Option(
|
|
303
|
+
"--max-lines",
|
|
304
|
+
"-m",
|
|
305
|
+
help="Maximum number of lines to include in the output (default: 500)"
|
|
306
|
+
)
|
|
307
|
+
] = 500
|
|
308
|
+
|
|
309
|
+
):
|
|
310
|
+
"""Output the .tost skeleton format for all files matching a specific subpath."""
|
|
311
|
+
configure_cli_logging(debug)
|
|
312
|
+
|
|
313
|
+
start_time = time.perf_counter()
|
|
314
|
+
try:
|
|
315
|
+
result = asyncio.run(skeleton_async(subpath, path, pretty=pretty, depth=depth, files_only=files_only))
|
|
316
|
+
lines = result.splitlines()
|
|
317
|
+
if len(lines) > max_lines:
|
|
318
|
+
result = "\n".join(lines[:max_lines]) + "\n...[OUTPUT TRUNCATED AT 500 LINES] - Use a higher '--max-lines <N>' to see more."
|
|
319
|
+
print(result)
|
|
320
|
+
except TostrError as e:
|
|
321
|
+
typer.secho(f"❌ Error: {e}", fg="red", err=True)
|
|
322
|
+
raise typer.Exit(code=1)
|
|
323
|
+
end_time = time.perf_counter()
|
|
324
|
+
elapsed_time = end_time - start_time
|
|
325
|
+
logger.debug(f"Finished in {elapsed_time:.4f} seconds.")
|
|
326
|
+
|
|
327
|
+
if __name__ == "__main__":
|
|
328
|
+
multiprocessing.freeze_support()
|
|
329
|
+
app()
|