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.
Files changed (36) hide show
  1. tostr-0.1.0/LICENSE +21 -0
  2. tostr-0.1.0/PKG-INFO +149 -0
  3. tostr-0.1.0/README.md +128 -0
  4. tostr-0.1.0/pyproject.toml +32 -0
  5. tostr-0.1.0/setup.cfg +4 -0
  6. tostr-0.1.0/src/tostr/__init__.py +0 -0
  7. tostr-0.1.0/src/tostr/cli.py +329 -0
  8. tostr-0.1.0/src/tostr/commands.py +197 -0
  9. tostr-0.1.0/src/tostr/core/__init__.py +6 -0
  10. tostr-0.1.0/src/tostr/core/builders.py +156 -0
  11. tostr-0.1.0/src/tostr/core/context/__init__.py +1 -0
  12. tostr-0.1.0/src/tostr/core/context/config.py +72 -0
  13. tostr-0.1.0/src/tostr/core/db.py +81 -0
  14. tostr-0.1.0/src/tostr/core/models.py +674 -0
  15. tostr-0.1.0/src/tostr/core/parser.py +98 -0
  16. tostr-0.1.0/src/tostr/core/providers.py +95 -0
  17. tostr-0.1.0/src/tostr/core/registry.py +387 -0
  18. tostr-0.1.0/src/tostr/core/serializer.py +174 -0
  19. tostr-0.1.0/src/tostr/core/utils/logger.py +62 -0
  20. tostr-0.1.0/src/tostr/exceptions.py +22 -0
  21. tostr-0.1.0/src/tostr/languages/__init__.py +0 -0
  22. tostr-0.1.0/src/tostr/languages/java/__init__.py +1 -0
  23. tostr-0.1.0/src/tostr/languages/java/builders.py +335 -0
  24. tostr-0.1.0/src/tostr/languages/java/language.py +4 -0
  25. tostr-0.1.0/src/tostr/languages/java/queries.py +19 -0
  26. tostr-0.1.0/src/tostr/llm/__init__.py +3 -0
  27. tostr-0.1.0/src/tostr/llm/base.py +69 -0
  28. tostr-0.1.0/src/tostr/llm/gemini.py +30 -0
  29. tostr-0.1.0/src/tostr/llm/prompts.py +38 -0
  30. tostr-0.1.0/src/tostr/server.py +251 -0
  31. tostr-0.1.0/src/tostr.egg-info/PKG-INFO +149 -0
  32. tostr-0.1.0/src/tostr.egg-info/SOURCES.txt +34 -0
  33. tostr-0.1.0/src/tostr.egg-info/dependency_links.txt +1 -0
  34. tostr-0.1.0/src/tostr.egg-info/entry_points.txt +2 -0
  35. tostr-0.1.0/src/tostr.egg-info/requires.txt +11 -0
  36. 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
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
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()