nooa-cli 0.0.7__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.
nooa_cli/completion.py ADDED
@@ -0,0 +1,253 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """nooa completion — Generate shell completion scripts.
4
+
5
+ Usage:
6
+ nooa completion bash # Print bash completion script
7
+ nooa completion zsh # Print zsh completion script
8
+ nooa completion fish # Print fish completion script
9
+ nooa completion install # Auto-install for current shell
10
+ """
11
+
12
+ import os
13
+ from pathlib import Path
14
+
15
+ import click
16
+
17
+ # Completion script templates — generated by Click 8.x, embedded to avoid
18
+ # subprocess overhead and capture issues.
19
+ _BASH_SCRIPT = r"""
20
+ %(complete_func)s() {
21
+ local IFS=$'\n'
22
+ COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \
23
+ COMP_CWORD=$COMP_CWORD \
24
+ %(complete_var)s=bash_complete $1 ) )
25
+ return 0
26
+ }
27
+
28
+ %(complete_func)ssetup() {
29
+ complete -o default -F %(complete_func)s %(prog_name)s
30
+ }
31
+
32
+ %(complete_func)ssetup;
33
+ """.strip()
34
+
35
+ _ZSH_SCRIPT = r"""
36
+ #compdef %(prog_name)s
37
+
38
+ %(complete_func)s() {
39
+ local -a completions
40
+ local -a completions_with_descriptions
41
+ local -a response
42
+ (( ! $+commands[%(prog_name)s] )) && return 1
43
+
44
+ response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) %(complete_var)s=zsh_complete %(prog_name)s)}")
45
+
46
+ for type key descr in ${response}; do
47
+ if [[ "$type" == "plain" ]]; then
48
+ if [[ "$descr" == "_" ]]; then
49
+ completions+=("$key")
50
+ else
51
+ completions_with_descriptions+=("$key":"$descr")
52
+ fi
53
+ elif [[ "$type" == "dir" ]]; then
54
+ _path_files -/
55
+ elif [[ "$type" == "file" ]]; then
56
+ _path_files -f
57
+ fi
58
+ done
59
+
60
+ if [ -n "$completions_with_descriptions" ]; then
61
+ _describe -V unsorted completions_with_descriptions -U
62
+ fi
63
+
64
+ if [ -n "$completions" ]; then
65
+ compadd -U -V unsorted -a completions
66
+ fi
67
+ }
68
+
69
+ if [[ $zsh_eval_context[-1] == loadautofun ]]; then
70
+ # autoload from fpath
71
+ %(complete_func)s "$@"
72
+ else
73
+ compdef %(complete_func)s %(prog_name)s
74
+ fi
75
+ """.strip()
76
+
77
+ _FISH_SCRIPT = r"""
78
+ complete -c %(prog_name)s -f -a "(env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) COMP_CWORD=(commandline -t) %(prog_name)s)"
79
+ """.strip()
80
+
81
+
82
+ def _render_script(template: str) -> str:
83
+ """Render a completion script template with nooa-specific values."""
84
+ return template % {
85
+ "complete_func": "_nooa_completion",
86
+ "complete_var": "_NOOA_COMPLETE",
87
+ "prog_name": "nooa",
88
+ }
89
+
90
+
91
+ @click.group()
92
+ def completion():
93
+ """Generate shell completion scripts for nooa.
94
+
95
+ \b
96
+ Quick setup (add to your shell profile):
97
+
98
+ # bash (~/.bashrc)
99
+ eval "$(_NOOA_COMPLETE=bash_source nooa)"
100
+
101
+ # zsh (~/.zshrc)
102
+ eval "$(_NOOA_COMPLETE=zsh_source nooa)"
103
+
104
+ # fish (~/.config/fish/config.fish)
105
+ _NOOA_COMPLETE=fish_source nooa | source
106
+ """
107
+
108
+
109
+ @completion.command()
110
+ def bash():
111
+ """Print bash completion script.
112
+
113
+ \b
114
+ Usage:
115
+ eval "$(nooa completion bash)"
116
+ # Or append to profile:
117
+ nooa completion bash >> ~/.bashrc
118
+ """
119
+ click.echo(_render_script(_BASH_SCRIPT))
120
+
121
+
122
+ @completion.command()
123
+ def zsh():
124
+ """Print zsh completion script.
125
+
126
+ \b
127
+ Usage:
128
+ eval "$(nooa completion zsh)"
129
+ # Or append to profile:
130
+ nooa completion zsh >> ~/.zshrc
131
+ """
132
+ click.echo(_render_script(_ZSH_SCRIPT))
133
+
134
+
135
+ @completion.command()
136
+ def fish():
137
+ """Print fish completion script.
138
+
139
+ \b
140
+ Usage:
141
+ nooa completion fish > ~/.config/fish/completions/nooa.fish
142
+ """
143
+ click.echo(_render_script(_FISH_SCRIPT))
144
+
145
+
146
+ @completion.command()
147
+ def install():
148
+ """Auto-detect shell and install completions.
149
+
150
+ Detects your current shell and appends the completion script
151
+ to the appropriate profile file.
152
+ """
153
+ shell = _detect_shell()
154
+ if not shell:
155
+ click.secho("Could not detect your shell. Use one of:", fg="red")
156
+ click.echo(" nooa completion bash")
157
+ click.echo(" nooa completion zsh")
158
+ click.echo(" nooa completion fish")
159
+ raise SystemExit(1)
160
+
161
+ click.echo(f"Detected shell: {shell}")
162
+
163
+ if shell == "bash":
164
+ profile = Path.home() / ".bashrc"
165
+ line = 'eval "$(_NOOA_COMPLETE=bash_source nooa)"'
166
+ elif shell == "zsh":
167
+ profile = Path.home() / ".zshrc"
168
+ line = 'eval "$(_NOOA_COMPLETE=zsh_source nooa)"'
169
+ elif shell == "fish":
170
+ profile = Path.home() / ".config" / "fish" / "completions" / "nooa.fish"
171
+ line = None # Fish uses a completions file with the script directly
172
+ else:
173
+ click.secho(f"Unsupported shell: {shell}", fg="red")
174
+ raise SystemExit(1)
175
+
176
+ # Remove stale pre-rename fish completions file if present.
177
+ if shell == "fish":
178
+ old_fish = Path.home() / ".config" / "fish" / "completions" / "nemo.fish"
179
+ if old_fish.exists():
180
+ old_fish.unlink()
181
+ click.secho(f"Removed stale pre-rename completions: {old_fish}", fg="yellow")
182
+
183
+ # Check if already installed; drop stale pre-rename lines. Old installs
184
+ # wrote `_NEMO_COMPLETE=... nooa` — now that the `nooa` binary
185
+ # exists, click ignores the wrong variable and the eval'd help text
186
+ # errors on every shell startup.
187
+ if profile.exists():
188
+ existing = _read_preserving_newlines(profile)
189
+ line_ending = _detect_line_ending(existing)
190
+ lines = existing.splitlines()
191
+ stale_indices = {
192
+ i for i, ln in enumerate(lines) if "_NEMO_COMPLETE" in ln and "_NOOA_COMPLETE" not in ln
193
+ }
194
+ # Also mark preceding comment lines that belong to the same block
195
+ # (e.g. "# nemo shell completions" immediately above a stale eval).
196
+ for i in sorted(stale_indices):
197
+ prev = i - 1
198
+ while prev >= 0 and prev not in stale_indices:
199
+ stripped = lines[prev].strip()
200
+ if stripped.startswith("#") and "nemo" in stripped.lower():
201
+ stale_indices.add(prev)
202
+ prev -= 1
203
+ else:
204
+ break
205
+ if stale_indices:
206
+ cleaned = line_ending.join(ln for i, ln in enumerate(lines) if i not in stale_indices)
207
+ profile.write_text(cleaned + (line_ending if cleaned else ""))
208
+ existing = cleaned
209
+ click.secho(
210
+ f"Removed stale _NEMO_COMPLETE completion line(s) from {profile}",
211
+ fg="yellow",
212
+ )
213
+ if "_NOOA_COMPLETE" in existing:
214
+ click.secho(f"Completions already installed in {profile}", fg="green")
215
+ return
216
+
217
+ if shell == "fish":
218
+ # Fish: write completions file
219
+ profile.parent.mkdir(parents=True, exist_ok=True)
220
+ script = _render_script(_FISH_SCRIPT)
221
+ profile.write_text(script + "\n")
222
+ click.secho(f"Completions written to {profile}", fg="green")
223
+ else:
224
+ # Bash/zsh: append eval one-liner to profile
225
+ with open(profile, "a") as f:
226
+ f.write(f"\n# nooa shell completions\n{line}\n")
227
+ click.secho(f"Completions added to {profile}", fg="green")
228
+ click.echo(f"Run: source {profile}")
229
+
230
+
231
+ def _detect_line_ending(text: str) -> str:
232
+ """Return the dominant line ending in *text*."""
233
+ if "\r\n" in text:
234
+ return "\r\n"
235
+ return "\n"
236
+
237
+
238
+ def _read_preserving_newlines(path: Path) -> str:
239
+ """Read file without Python's universal newline translation."""
240
+ with open(path, newline="") as f:
241
+ return f.read()
242
+
243
+
244
+ def _detect_shell() -> str | None:
245
+ """Detect the current shell."""
246
+ shell = os.environ.get("SHELL", "")
247
+ if "zsh" in shell:
248
+ return "zsh"
249
+ elif "bash" in shell:
250
+ return "bash"
251
+ elif "fish" in shell:
252
+ return "fish"
253
+ return None
File without changes
@@ -0,0 +1,351 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """Tree-sitter backend for RepoTools.
4
+
5
+ Provides AST-aware code analysis: symbol extraction and reference finding
6
+ using tree-sitter grammars. Falls back gracefully if grammars are unavailable.
7
+
8
+ Usage (internal — called by repo_tools.py)::
9
+
10
+ from nooa_cli.tools._tree_sitter_backend import (
11
+ ts_extract_symbols,
12
+ ts_find_references,
13
+ TREE_SITTER_AVAILABLE,
14
+ )
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import logging
20
+ from pathlib import Path
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ try:
25
+ import tree_sitter as ts
26
+
27
+ TREE_SITTER_AVAILABLE = True
28
+ except ImportError:
29
+ TREE_SITTER_AVAILABLE = False
30
+
31
+ # Language grammar loaders (lazy-loaded)
32
+ _PARSERS: dict[str, ts.Parser] = {}
33
+
34
+ # Map language name → grammar module and language loader function.
35
+ _GRAMMAR_MODULES = {
36
+ "python": ("tree_sitter_python", "language"),
37
+ "javascript": ("tree_sitter_javascript", "language"),
38
+ "typescript": ("tree_sitter_typescript", "language_typescript"),
39
+ "tsx": ("tree_sitter_typescript", "language_tsx"),
40
+ "go": ("tree_sitter_go", "language"),
41
+ "rust": ("tree_sitter_rust", "language"),
42
+ }
43
+
44
+ # Tree-sitter queries for extracting definitions per language
45
+ _DEFINITION_QUERIES = {
46
+ "python": """
47
+ (function_definition name: (identifier) @name) @def
48
+ (class_definition name: (identifier) @name) @def
49
+ """,
50
+ "javascript": """
51
+ (function_declaration name: (identifier) @name) @def
52
+ (class_declaration name: (identifier) @name) @def
53
+ (method_definition name: (property_identifier) @name) @def
54
+ (lexical_declaration
55
+ (variable_declarator
56
+ name: (identifier) @name
57
+ value: (arrow_function))) @def
58
+ """,
59
+ "typescript": """
60
+ (function_declaration name: (identifier) @name) @def
61
+ (class_declaration name: (identifier) @name) @def
62
+ (interface_declaration name: (type_identifier) @name) @def
63
+ (type_alias_declaration name: (type_identifier) @name) @def
64
+ (method_definition name: (property_identifier) @name) @def
65
+ """,
66
+ "go": """
67
+ (function_declaration name: (identifier) @name) @def
68
+ (method_declaration name: (field_identifier) @name) @def
69
+ (type_declaration (type_spec name: (type_identifier) @name)) @def
70
+ """,
71
+ "rust": """
72
+ (function_item name: (identifier) @name) @def
73
+ (struct_item name: (type_identifier) @name) @def
74
+ (enum_item name: (type_identifier) @name) @def
75
+ (trait_item name: (type_identifier) @name) @def
76
+ (impl_item type: (type_identifier) @name) @def
77
+ """,
78
+ }
79
+
80
+ # Tree-sitter queries for finding references (call sites, imports, usages)
81
+ _DEFINITION_QUERIES["tsx"] = _DEFINITION_QUERIES["typescript"]
82
+
83
+ _REFERENCE_QUERIES = {
84
+ "python": """
85
+ (call function: (identifier) @ref)
86
+ (call function: (attribute attribute: (identifier) @ref))
87
+ (import_from_statement name: (dotted_name (identifier) @ref))
88
+ (identifier) @ref
89
+ """,
90
+ "javascript": """
91
+ (call_expression function: (identifier) @ref)
92
+ (call_expression function: (member_expression property: (property_identifier) @ref))
93
+ (identifier) @ref
94
+ """,
95
+ "typescript": """
96
+ (call_expression function: (identifier) @ref)
97
+ (call_expression function: (member_expression property: (property_identifier) @ref))
98
+ (type_identifier) @ref
99
+ (identifier) @ref
100
+ """,
101
+ "go": """
102
+ (call_expression function: (identifier) @ref)
103
+ (call_expression function: (selector_expression field: (field_identifier) @ref))
104
+ (identifier) @ref
105
+ """,
106
+ "rust": """
107
+ (call_expression function: (identifier) @ref)
108
+ (call_expression function: (field_expression field: (field_identifier) @ref))
109
+ (identifier) @ref
110
+ """,
111
+ }
112
+ _REFERENCE_QUERIES["tsx"] = _REFERENCE_QUERIES["typescript"]
113
+
114
+
115
+ def _get_parser(lang: str) -> ts.Parser | None:
116
+ """Get or create a tree-sitter parser for the given language."""
117
+ if not TREE_SITTER_AVAILABLE:
118
+ return None
119
+
120
+ if lang in _PARSERS:
121
+ return _PARSERS[lang]
122
+
123
+ grammar = _GRAMMAR_MODULES.get(lang)
124
+ if not grammar:
125
+ return None
126
+ mod_name, loader_name = grammar
127
+
128
+ try:
129
+ import importlib
130
+
131
+ grammar_mod = importlib.import_module(mod_name)
132
+ language_fn = getattr(grammar_mod, loader_name)
133
+ language = ts.Language(language_fn())
134
+ parser = ts.Parser(language)
135
+ _PARSERS[lang] = parser
136
+ return parser
137
+ except (ImportError, AttributeError, Exception) as e:
138
+ logger.debug(f"tree-sitter grammar not available for {lang}: {e}")
139
+ return None
140
+
141
+
142
+ def _query_captures(query: ts.Query, root_node: ts.Node) -> list[tuple[ts.Node, str]]:
143
+ legacy_captures = getattr(query, "captures", None)
144
+ if legacy_captures is not None:
145
+ return legacy_captures(root_node)
146
+
147
+ captures = ts.QueryCursor(query).captures(root_node)
148
+ if isinstance(captures, dict):
149
+ flattened = [
150
+ (node, capture_name) for capture_name, nodes in captures.items() for node in nodes
151
+ ]
152
+ return sorted(flattened, key=lambda item: item[0].start_byte)
153
+ return captures
154
+
155
+
156
+ def ts_extract_symbols(path: Path, lang: str, max_symbols: int = 200) -> list[str] | None:
157
+ """Extract symbol definitions using tree-sitter AST parsing.
158
+
159
+ Returns a list of formatted symbol lines, or None if tree-sitter
160
+ is not available for this language (caller should fall back to regex).
161
+ """
162
+ parser = _get_parser(lang)
163
+ if parser is None:
164
+ return None
165
+
166
+ try:
167
+ source = path.read_bytes()
168
+ tree = parser.parse(source)
169
+ except (OSError, Exception) as e:
170
+ logger.debug(f"tree-sitter parse failed for {path}: {e}")
171
+ return None
172
+
173
+ query_src = _DEFINITION_QUERIES.get(lang)
174
+ if not query_src:
175
+ return None
176
+
177
+ try:
178
+ language = parser.language
179
+ query = language.query(query_src)
180
+ except Exception as e:
181
+ logger.debug(f"tree-sitter query compilation failed for {lang}: {e}")
182
+ return None
183
+
184
+ symbols: list[str] = []
185
+ captures = _query_captures(query, tree.root_node)
186
+
187
+ # Process captures — look for @name captures paired with @def
188
+ seen_lines: set[int] = set()
189
+ for node, capture_name in captures:
190
+ if capture_name != "name":
191
+ continue
192
+ if len(symbols) >= max_symbols:
193
+ break
194
+
195
+ line = node.start_point[0] + 1 # 1-indexed
196
+ if line in seen_lines:
197
+ continue
198
+ seen_lines.add(line)
199
+
200
+ name = source[node.start_byte : node.end_byte].decode("utf-8", errors="replace")
201
+
202
+ # Determine kind from the enclosing definition node.
203
+ parent = node.parent
204
+ kind = _node_to_kind(parent, lang, source)
205
+
206
+ # Calculate indent
207
+ indent = node.start_point[1]
208
+ prefix = " " * (indent // 4) if indent > 0 else ""
209
+ symbols.append(f" {line:4d} {prefix}{kind} {name}")
210
+
211
+ return symbols if symbols else None
212
+
213
+
214
+ def ts_find_references(
215
+ path: Path, lang: str, name: str, max_results: int = 100
216
+ ) -> list[tuple[int, str]] | None:
217
+ """Find references to a symbol using tree-sitter AST parsing.
218
+
219
+ Returns a list of (line_number, line_text) tuples, or None if
220
+ tree-sitter is not available (caller should fall back to regex).
221
+
222
+ Filters out definition sites — only returns usages/call sites.
223
+ """
224
+ parser = _get_parser(lang)
225
+ if parser is None:
226
+ return None
227
+
228
+ try:
229
+ source = path.read_bytes()
230
+ tree = parser.parse(source)
231
+ except (OSError, Exception):
232
+ return None
233
+
234
+ query_src = _REFERENCE_QUERIES.get(lang)
235
+ if not query_src:
236
+ return None
237
+
238
+ try:
239
+ language = parser.language
240
+ query = language.query(query_src)
241
+ except Exception:
242
+ return None
243
+
244
+ lines = source.decode("utf-8", errors="replace").splitlines()
245
+ captures = _query_captures(query, tree.root_node)
246
+
247
+ # Handle qualified names (e.g. "TraceExplorer.from_file" → match "from_file")
248
+ search_name = name.split(".")[-1] if "." in name else name
249
+ qualifier = name.split(".")[0] if "." in name else None
250
+
251
+ results: list[tuple[int, str]] = []
252
+ seen_lines: set[int] = set()
253
+
254
+ for node, capture_name in captures:
255
+ if capture_name != "ref":
256
+ continue
257
+ if len(results) >= max_results:
258
+ break
259
+
260
+ node_text = source[node.start_byte : node.end_byte].decode("utf-8", errors="replace")
261
+ if node_text != search_name:
262
+ continue
263
+
264
+ line = node.start_point[0] + 1 # 1-indexed
265
+ if line in seen_lines:
266
+ continue
267
+
268
+ # Skip definition sites
269
+ parent = node.parent
270
+ if parent and _is_definition_node(parent.type, lang):
271
+ continue
272
+
273
+ # If qualified name, check that qualifier appears on the same line
274
+ if qualifier and line <= len(lines):
275
+ line_text = lines[line - 1]
276
+ if qualifier not in line_text:
277
+ continue
278
+
279
+ seen_lines.add(line)
280
+ line_text = lines[line - 1] if line <= len(lines) else ""
281
+ results.append((line, line_text.strip()))
282
+
283
+ return results if results else None
284
+
285
+
286
+ def _node_to_kind(node: ts.Node | None, lang: str, source: bytes) -> str:
287
+ """Map a tree-sitter definition node to a human-readable kind string."""
288
+ if node is None:
289
+ return "symbol"
290
+
291
+ if lang == "python" and node.type == "function_definition":
292
+ line = source.splitlines()[node.start_point[0]].lstrip()
293
+ if line.startswith(b"async def "):
294
+ return "async function"
295
+
296
+ if lang == "go" and node.type == "type_spec":
297
+ for child in node.children:
298
+ if child.type == "struct_type":
299
+ return "struct"
300
+ if child.type == "interface_type":
301
+ return "interface"
302
+
303
+ return _node_type_to_kind(node.type, lang)
304
+
305
+
306
+ def _node_type_to_kind(node_type: str, lang: str) -> str:
307
+ """Map tree-sitter node type to a human-readable kind string."""
308
+ mapping = {
309
+ "function_definition": "function",
310
+ "function_declaration": "function",
311
+ "function_item": "function",
312
+ "method_definition": "method",
313
+ "method_declaration": "method",
314
+ "class_definition": "class",
315
+ "class_declaration": "class",
316
+ "struct_item": "struct",
317
+ "enum_item": "enum",
318
+ "trait_item": "trait",
319
+ "impl_item": "impl",
320
+ "interface_declaration": "interface",
321
+ "type_alias_declaration": "type",
322
+ "type_declaration": "type",
323
+ "type_spec": "type",
324
+ "lexical_declaration": "function",
325
+ "variable_declarator": "function",
326
+ }
327
+ return mapping.get(node_type, "symbol")
328
+
329
+
330
+ def _is_definition_node(node_type: str, lang: str) -> bool:
331
+ """Return True if the node type represents a definition (not a reference)."""
332
+ def_types = {
333
+ "function_definition",
334
+ "function_declaration",
335
+ "function_item",
336
+ "method_definition",
337
+ "method_declaration",
338
+ "class_definition",
339
+ "class_declaration",
340
+ "struct_item",
341
+ "enum_item",
342
+ "trait_item",
343
+ "impl_item",
344
+ "interface_declaration",
345
+ "type_alias_declaration",
346
+ "type_declaration",
347
+ "type_spec",
348
+ "lexical_declaration",
349
+ "variable_declarator",
350
+ }
351
+ return node_type in def_types
@@ -0,0 +1,71 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """Async-native composable shell piping in Python with method-chaining streams."""
4
+
5
+ from nooa.skill import Skill
6
+ from nooa_cli.tools.pyp.errors import PipeError as PipeError
7
+ from nooa_cli.tools.pyp.errors import Result as Result
8
+ from nooa_cli.tools.pyp.errors import make_pipe_error as make_pipe_error
9
+ from nooa_cli.tools.pyp.sources import arun as arun
10
+ from nooa_cli.tools.pyp.sources import cat as cat
11
+ from nooa_cli.tools.pyp.sources import empty as empty
12
+ from nooa_cli.tools.pyp.sources import find as find
13
+ from nooa_cli.tools.pyp.sources import glob as glob
14
+ from nooa_cli.tools.pyp.sources import items as items
15
+ from nooa_cli.tools.pyp.sources import lines as lines
16
+ from nooa_cli.tools.pyp.sources import rg as rg
17
+ from nooa_cli.tools.pyp.sources import run as run
18
+ from nooa_cli.tools.pyp.sources import seq as seq
19
+ from nooa_cli.tools.pyp.sources import stdin as stdin
20
+ from nooa_cli.tools.pyp.stream import Stream as Stream
21
+
22
+
23
+ class Pyp(Skill):
24
+ """Async-native shell piping — source().transforms().sink().
25
+
26
+ Build pipelines: pick a source, chain transforms, await a sink.
27
+ All streaming, all async, all non-blocking.
28
+
29
+ Examples::
30
+
31
+ # Search logs
32
+ errors = await self.pyp.cat("app.log").grep("ERROR").head(10).collect()
33
+
34
+ # Find files
35
+ files = await self.pyp.find("src", name="*.py").sort().collect()
36
+
37
+ # Ripgrep → count
38
+ n = await self.pyp.rg("TODO", type_filter="py").count()
39
+
40
+ # Shell command → table
41
+ table = await self.pyp.run("ps aux").head(5).table()
42
+
43
+ # Apply function to each line
44
+ out = await self.pyp.run("ls").xargs(process).collect()
45
+
46
+ # Pipeline → file
47
+ n = await self.pyp.cat("data.csv").grep("error").write("errors.csv")
48
+
49
+ # Stream via ShellTools
50
+ fails = await self.pyp.arun(self.shell, "make test").grep("FAIL").collect()
51
+
52
+ Transforms (on any Stream): .grep() .head() .tail() .sort() .uniq()
53
+ .cut() .sed() .map() .filter() .xargs() .wc() .skip() .strip()
54
+ .tee() .flatten() .take_while() .drop_while() .pipe()
55
+
56
+ Sinks: .collect() .text() .first() .last() .count() .table()
57
+ .json() .result() .write() .to_set() .to_dict()
58
+ """
59
+
60
+ # Expose all source functions as instance methods
61
+ cat = staticmethod(cat)
62
+ run = staticmethod(run)
63
+ arun = staticmethod(arun)
64
+ find = staticmethod(find)
65
+ glob = staticmethod(glob)
66
+ rg = staticmethod(rg)
67
+ seq = staticmethod(seq)
68
+ lines = staticmethod(lines)
69
+ items = staticmethod(items)
70
+ empty = staticmethod(empty)
71
+ stdin = staticmethod(stdin)