pycli-dsl 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.
@@ -0,0 +1,601 @@
1
+ Metadata-Version: 2.3
2
+ Name: pycli-dsl
3
+ Version: 0.1.0
4
+ Summary: A Python-compatible DevOps DSL transpiler extending Python with first-class shell command execution
5
+ Keywords: devops,dsl,transpiler,shell,bash,cli
6
+ Author: Vito Vessia
7
+ Author-email: Vito Vessia <vito.vessia@wolterskluwer.com>
8
+ License: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: System Administrators
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: System :: Systems Administration
16
+ Classifier: Topic :: Software Development :: Compilers
17
+ Requires-Python: >=3.12
18
+ Project-URL: Homepage, https://github.com/vitovex/pycli
19
+ Project-URL: Repository, https://github.com/vitovex/pycli
20
+ Project-URL: Issues, https://github.com/vitovex/pycli/issues
21
+ Project-URL: Documentation, https://github.com/vitovex/pycli#readme
22
+ Description-Content-Type: text/markdown
23
+
24
+ # pycli
25
+
26
+ [![CI Pipeline](https://github.com/vitovex/pycli/actions/workflows/ci.yml/badge.svg)](https://github.com/vitovex/pycli/actions/workflows/ci.yml)
27
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://www.python.org/)
28
+ [![Platform](https://img.shields.io/badge/platform-linux%20%7C%20macos%20%7C%20windows-lightgrey.svg)](https://github.com/vitovex/pycli/actions)
29
+
30
+ A lightweight Python-compatible DevOps DSL that extends Python with first-class shell command execution.
31
+
32
+ ## Overview
33
+
34
+ `pycli` transpiles `.spy` files into pure standard Python (`.py`), providing:
35
+ - **Python Readability**: Natural Python syntax and standard ecosystem compatibility.
36
+ - **PowerShell-like Command Invocation**: Run shell commands directly with `$(...)`.
37
+ - **Bash-like Command Composition**: Pipelines (`|`), redirections (`>`, `>>`, `<`), and subcommands.
38
+ - **Native Object Handling**: Seamless integration with Python objects, string interpolation `{var}`, and list expansion (`{*files}`).
39
+ - **Cross-Platform Compatibility**: Tested and verified across Linux (`ubuntu-latest`), macOS (`macos-latest`), and Windows (`windows-latest`).
40
+ - **Zero Custom VM**: Transpiled directly to standard Python and executed on standard CPython.
41
+
42
+ See the full specification in [docs/pycli-grammar.md](docs/pycli-grammar.md).
43
+
44
+ ## Example
45
+
46
+ ### Source DSL (`deploy.spy`)
47
+
48
+ ```python
49
+ target_env = "production"
50
+ branch = $(git branch --show-current).text
51
+
52
+ # 1. Pipelines (|) and line streaming
53
+ live_pods = $(kubectl get pods -n {target_env} | grep -E 'Running|Pending').lines
54
+ for pod in live_pods:
55
+ print(f"Active pod: {pod}")
56
+
57
+ # 2. Context managers: temporary directory (cd) and environment variables (env)
58
+ with cd("frontend"), env(NODE_ENV=target_env):
59
+ $(npm ci)!
60
+ build_log = $(npm run build).tee
61
+
62
+ # 3. List expansion (splat), structured JSON output, and safe probing (?)
63
+ artifacts = ["dist/app.js", "dist/app.css"]
64
+ $(gzip -k {*artifacts})
65
+
66
+ cluster = $(az aks show --name prod-cluster --resource-group {target_env}).json
67
+ print(f"Cluster FQDN: {cluster.fqdn}")
68
+
69
+ status = $(curl -sSf http://localhost:8080/health)?
70
+ if status:
71
+ print("Health check passed successfully!")
72
+ ```
73
+
74
+ ### Transpiled Python (`deploy.py`)
75
+
76
+ ```python
77
+ from pycli.runtime import cd, env, run, run_expanded
78
+
79
+ target_env = "production"
80
+ branch = run("git branch --show-current").text
81
+
82
+ # 1. Pipelines (|) and line streaming
83
+ live_pods = run(f"""kubectl get pods -n {target_env} | grep -E 'Running|Pending'""").lines
84
+ for pod in live_pods:
85
+ print(f"Active pod: {pod}")
86
+
87
+ # 2. Context managers: temporary directory (cd) and environment variables (env)
88
+ with cd("frontend"), env(NODE_ENV=target_env):
89
+ run("npm ci", capture=False, check=True)
90
+ build_log = run("npm run build", tee=True)
91
+
92
+ # 3. List expansion (splat), structured JSON output, and safe probing (?)
93
+ artifacts = ["dist/app.js", "dist/app.css"]
94
+ run_expanded("gzip", "-k", *artifacts, capture=False)
95
+
96
+ cluster = run(f"az aks show --name prod-cluster --resource-group {target_env}").json
97
+ print(f"Cluster FQDN: {cluster.fqdn}")
98
+
99
+ status = run("curl -sSf http://localhost:8080/health", suppress_errors=True)
100
+ if status:
101
+ print("Health check passed successfully!")
102
+ ```
103
+
104
+ ## CLI Usage
105
+
106
+ ### Global Installation (Centralized Command)
107
+
108
+ You can install `pycli` globally into your system `PATH` using `uv tool`:
109
+
110
+ ```powershell
111
+ uv tool install --editable . --force
112
+ ```
113
+
114
+ This registers two commands globally on your machine:
115
+ - **`spy`**: Ultra-concise runner for `.spy` scripts.
116
+ - **`pycli`**: The full CLI tool with subcommands.
117
+
118
+ Once installed, you can run `.spy` scripts from **any folder or terminal**:
119
+ ```powershell
120
+ spy script.spy
121
+ # or
122
+ pycli script.spy
123
+ ```
124
+
125
+ ### Direct Script Execution without Global Install
126
+
127
+ If working inside this repository with `uv`:
128
+ ```powershell
129
+ uv run pycli script.spy
130
+ ```
131
+
132
+ ### Transpile `.spy` to `.py`
133
+
134
+ ```powershell
135
+ # Output to stdout with syntax coloring
136
+ spy transpile script.spy
137
+
138
+ # Output to a file (clean Python without ANSI codes)
139
+ spy transpile script.spy -o script.py
140
+
141
+ # Force / disable color
142
+ spy transpile script.spy --color
143
+ spy transpile script.spy --no-color
144
+
145
+ # Validate generated Python code with ast.parse
146
+ spy transpile script.spy --validate
147
+
148
+ # Disable automatic shell_quote() sanitization on interpolations
149
+ spy transpile script.spy --unsafe-interpolation
150
+ ```
151
+
152
+ ### Run `.spy` Scripts
153
+
154
+ ```powershell
155
+ # Run a script directly
156
+ spy run script.spy
157
+ # or simply
158
+ spy script.spy
159
+
160
+ # Run with generated Python syntax validation
161
+ spy run --validate script.spy
162
+
163
+ # Run with unsafe interpolation (disables shell_quote)
164
+ spy run --unsafe-interpolation script.spy
165
+
166
+ # Run with warning on untrusted external scripts
167
+ spy run --warn-external script.spy
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Security & Robustness
173
+
174
+ ### Automatic Shell Interpolation Sanitization
175
+ By default, all variable interpolations `{var}` and dynamic redirection targets are wrapped with `shell_quote(var)` (`shlex.quote`) during transpilation. This protects against shell injection attacks if variables contain metacharacters (`;`, `&&`, `|`, etc.).
176
+ If raw, unquoted shell syntax expansion is explicitly needed, pass `--unsafe-interpolation` or use `transpile(..., unsafe_interpolation=True)`.
177
+
178
+ ### Execution Privilege Model (Not a Sandbox)
179
+ `pycli` executes `.spy` files on standard CPython runtimes with the full privileges and environment of the user running the process. It is **not** a sandbox. When executing `.spy` scripts from external or untrusted sources, use `--warn-external` and verify the script contents.
180
+
181
+ ### Command Execution Controls
182
+ The runtime functions `run()`, `run_expanded()`, and `async_run()` support robust controls:
183
+ - **`timeout`**: Terminate hanging processes and raise `CommandTimeoutError`.
184
+ - **`encoding`**: Custom text decoding (default `"utf-8"`, configurable to CP1252, Latin-1, etc.).
185
+ - **`max_output_bytes`**: Cap memory consumption by truncating stdout/stderr beyond a threshold (`res.truncated = True`).
186
+
187
+
188
+
189
+
190
+ ## Supported Language Features
191
+
192
+ | Feature | Syntax Example | Target Python Equivalent |
193
+ |---|---|---|
194
+ | **Statement Form** | `$(git status)` | `run("git status", capture=False)` (streams output to console) |
195
+ | **Expression Form** | `res = $(git status)` | `res = run("git status")` (captures stdout/stderr) |
196
+ | **Strict Mode** | `$(git status)!` | `run("git status", capture=False, check=True)` |
197
+ | **Safe Mode** | `$(curl http://...) ?` | `run(..., suppress_errors=True)` (no exception on non-zero exit code) |
198
+ | **Background / Async** | `job = $(docker build .) &` | `run_bg(...)` returning `BackgroundJob` (`.wait()`, `.poll()`, `.kill()`) |
199
+ | **Async / Await** | `res = await $(git pull)` | `await async_run(...)` for `asyncio` workflows |
200
+ | **Live Stream & Capture** | `res = $(npm test).tee` | `run(..., tee=True)` (live streaming to console + captured in `res`) |
201
+ | **Stdin Piping** | `$(kubectl apply -f -).input(yaml)` | `run(..., input=yaml)` |
202
+ | **Output Line Iteration** | `for line in $(git log): ...` | Direct iteration over `res`, or `res.lines` and `res.text` |
203
+ | **Context Managers** | `with cd(dir):`, `with env(K="V"):` | Temporary directory and environment variable scoping |
204
+ | **Quote Interpolation** | `$(echo "{name}" '{raw}')` | Double quotes interpolate `{expr}`; single quotes stay strictly literal |
205
+ | **List Expansion (Splat)** | `$(rm {*files})` | `run_expanded("rm", *files, capture=False)` |
206
+ | **Pipelines** | `$(kubectl get pods \| grep api)` | `run("kubectl get pods \| grep api", capture=False)` |
207
+ | **Redirection** | `$(git status > status.txt)` | `run("git status > status.txt", capture=False)` |
208
+ | **Subcommands** | `$(echo $(git branch --show-current))` | `run("echo $(git branch --show-current)", capture=False)` |
209
+ | **Truthiness** | `if $(git diff --quiet): ...` | `if run("git diff --quiet"): ...` (truthy if `exit_code == 0`) |
210
+ | **Structured Output (JSON)** | `vms = $(az vm list).json` | Navigable `DynamicObj` via `vm.name` or `vm["name"]` |
211
+ | **Interactive REPL** | `spy repl` or `spy` | Interactive shell with on-the-fly transpilation |
212
+ | **CommandResult Properties** | `res = $(git status)` | `res.stdout`, `res.stderr`, `res.exit_code`, `res.lines`, `res.text` |
213
+ | **Modular .spy Imports** | `import devops_utils` | Seamlessly import `.spy` files and packages via Python `importlib` hook |
214
+
215
+ ---
216
+
217
+ ## Complete Language Syntax Reference
218
+
219
+ `spy` is a superset of standard Python. Everything that is valid in Python 3.12+ is fully valid in `.spy`. `spy` introduces the **Command Expression** `$(...)` for seamless command-line execution and shell orchestration.
220
+
221
+ ### 1. Command Invocation Forms
222
+
223
+ #### Statement Form (Unassigned)
224
+ When a command expression appears as a standalone line or single-line statement:
225
+ ```python
226
+ $(terraform init)
227
+ if should_apply: $(terraform apply -auto-approve)
228
+ ```
229
+ - **Execution**: The command is executed and its output (`stdout` and `stderr`) is streamed live to the console in real-time.
230
+ - **Return value**: Discarded (`capture=False`).
231
+
232
+ #### Expression Form (Assigned / Inline)
233
+ When a command expression is assigned to a variable, passed as a function argument, or used in an expression:
234
+ ```python
235
+ current_branch = $(git branch --show-current)
236
+ log_output = $(git log -n 10).text
237
+ ```
238
+ - **Execution**: The output is captured silently and returned as a `CommandResult` instance.
239
+
240
+ ---
241
+
242
+ ### 2. Execution Modifiers
243
+
244
+ Modifiers are placed immediately after the closing parenthesis `)` of a command:
245
+
246
+ | Modifier | Syntax | Behavior | Python Equivalent |
247
+ |---|---|---|---|
248
+ | **Strict (`!`)** | `$(cmd)!` | Raises `CommandError` if exit code != 0 | `run(..., check=True)` |
249
+ | **Safe (`?`)** | `$(cmd)?` | Suppresses errors; never raises exceptions on failure | `run(..., suppress_errors=True)` |
250
+ | **Background (`&`)** | `job = $(cmd) &` | Spawns in background non-blockingly; returns `BackgroundJob` | `run_bg(...)` |
251
+
252
+ #### Examples:
253
+ ```python
254
+ # 1. Strict mode: abort pipeline if build fails
255
+ try:
256
+ $(docker build -t app:latest .)!
257
+ except Exception as err:
258
+ print(f"Build failed with exit code: {err.result.exit_code}")
259
+
260
+ # 2. Safe mode: probe an endpoint or optional service without try/except
261
+ probe = $(curl -sSf http://localhost:8080/health)?
262
+ if probe:
263
+ print("Service is healthy!")
264
+ else:
265
+ print(f"Service offline (exit code: {probe.exit_code})")
266
+
267
+ # 3. Background jobs: run long-running tasks concurrently (&)
268
+ job = $(mvn clean package) &
269
+ print("Maven build started in background...")
270
+ result = job.wait()
271
+ print(f"Build finished with code: {result.exit_code}")
272
+
273
+ # 3.1 Waiting for multiple parallel background jobs with wait_all(...)
274
+ j_api = $(deploy-service api) &
275
+ j_web = $(deploy-service web) &
276
+ j_db = $(deploy-service db) &
277
+
278
+ # Accepts variable arguments wait_all(j1, j2, ...) or a list wait_all([j1, j2, ...]):
279
+ all_results = wait_all(j_api, j_web, j_db)
280
+ for res in all_results:
281
+ print(f"Completed: {res.command} (exit code: {res.exit_code})")
282
+
283
+ # 4. Async / Await inside coroutines
284
+ async def pull_repo(name: str):
285
+ res = await $(git -C {name} pull origin main)
286
+ return res.stdout
287
+
288
+ # 4.1 Running multiple async commands in parallel with asyncio.gather
289
+ async def update_all_microservices():
290
+ repos = ["frontend", "backend", "worker"]
291
+ # All pull commands execute concurrently:
292
+ results = await asyncio.gather(*(pull_repo(r) for r in repos))
293
+ print(f"Updated {len(results)} repositories simultaneously.")
294
+ ```
295
+
296
+ ---
297
+
298
+ ### 3. Chaining Methods & Modifiers
299
+
300
+ You can chain properties and helper methods directly onto command expressions:
301
+
302
+ #### `.json` — Structured JSON Output
303
+ Automatically parses JSON standard output into a navigable `DynamicObj`:
304
+ ```python
305
+ pods = $(kubectl get pods -o json).json
306
+ for item in pods.items:
307
+ print(f"Pod: {item.metadata.name} | Status: {item.status.phase}")
308
+ # Supports both dot access and dict access:
309
+ print(f"Namespace: {item['metadata']['namespace']}")
310
+ ```
311
+
312
+ #### `.tee` — Live Console Streaming + Output Capture
313
+ Streams stdout/stderr in real-time to the console while simultaneously capturing the complete result in the variable:
314
+ ```python
315
+ # Output is displayed immediately on screen AND stored in 'test_run'
316
+ test_run = $(pytest tests/ -v).tee
317
+ if test_run.exit_code != 0:
318
+ print("Failed test log:", test_run.stderr)
319
+ ```
320
+
321
+ #### `.input(...)` — Feeding Stdin Data
322
+ Pipes string or binary data directly into the standard input of the subprocess:
323
+ ```python
324
+ manifest = """
325
+ apiVersion: v1
326
+ kind: ConfigMap
327
+ metadata:
328
+ name: app-config
329
+ """
330
+ $(kubectl apply -f -).input(manifest)
331
+ ```
332
+
333
+ #### `.lines` & `.text`
334
+ - `.lines`: Returns a `list[str]` of non-empty lines from `stdout` (stripped of trailing newlines).
335
+ - `.text`: Returns the trimmed `stdout` string (`stdout.strip()`).
336
+ ```python
337
+ branches = $(git branch --list).lines
338
+ first_line = $(head -n 1 file.txt).text
339
+ ```
340
+
341
+ ---
342
+
343
+ ### 4. Interpolation & Quoting Semantics
344
+
345
+ `spy` provides precise rules for parameter interpolation to keep shell scripts intuitive:
346
+
347
+ #### Unquoted Variable / Expression Interpolation
348
+ Any Python expression enclosed in `{...}` is evaluated and inserted into the command string:
349
+ ```python
350
+ target_cluster = "prod-us-east-1"
351
+ $(kubectl config use-context {target_cluster})
352
+ $(az vm list --resource-group {config.resource_group})
353
+ ```
354
+
355
+ #### Double Quotes (`"..."`) — Interpolation Enabled
356
+ Double-quoted command strings expand `{expression}`:
357
+ ```python
358
+ name = "World"
359
+ $(echo "Hello, {name}!")
360
+ # Evaluates to: echo "Hello, World!"
361
+ ```
362
+
363
+ #### Single Quotes (`'...'`) — Strictly Literal
364
+ Single-quoted command strings preserve braces literally. No interpolation occurs inside single quotes. This is critical for shell tools like `awk`, regex patterns, or inline sub-scripts:
365
+ ```python
366
+ # Braces remain literal {print $1}:
367
+ $(awk '{print $1}' access.log)
368
+
369
+ # Regex stays literal:
370
+ $(grep -E '^[0-9]{4}-[0-9]{2}' server.log)
371
+ ```
372
+
373
+ #### List Expansion / Splat (`{*iterable}`)
374
+ Expands a Python list, tuple, or iterable into space-separated command-line arguments:
375
+ ```python
376
+ files = ["service.py", "models.py", "utils.py"]
377
+ $(ruff check {*files})
378
+ # Transpiles to: run_expanded("ruff", "check", *files, capture=False)
379
+ ```
380
+
381
+ ---
382
+
383
+ ### 5. Preserved Shell Semantics
384
+
385
+ `spy` passes command strings to the underlying shell without interfering with native shell operators.
386
+
387
+ #### Pipelines (`|`)
388
+ Connect the standard output of one command directly to the standard input of the next:
389
+ ```python
390
+ # 1. Pipeline in statement form (streaming output directly to terminal)
391
+ $(kubectl get pods -n prod | grep -v Completed | sort)
392
+
393
+ # 2. Pipeline in expression form (captured and iterated)
394
+ failed_jobs = $(docker ps -a | grep "Exited (" | awk '{print $1}').lines
395
+ for container_id in failed_jobs:
396
+ print(f"Removing dead container: {container_id}")
397
+ $(docker rm {container_id})
398
+
399
+ # 3. Chaining with Python processing
400
+ build_errors = $(cargo check 2>&1 | grep "error\[E").lines
401
+ if build_errors:
402
+ print(f"Found {len(build_errors)} compile errors:")
403
+ for err in build_errors:
404
+ print(" -", err)
405
+ ```
406
+
407
+ #### Redirections (`>`, `>>`, `<`)
408
+ Direct process outputs or inputs to and from filesystem files:
409
+ ```python
410
+ # Overwrite file with stdout (>)
411
+ $(terraform output -json > tf_outputs.json)
412
+
413
+ # Append to log file (>>)
414
+ $(date >> deployment.log)
415
+ $(echo "Deployed by {user} on {branch}" >> deployment.log)
416
+
417
+ # Read input from file (<)
418
+ $(mysql -u root -p{db_pass} my_database < migration.sql)!
419
+ ```
420
+
421
+ #### Subcommands (`$(...)`)
422
+ Inner shell command substitutions are handled directly by the shell runtime:
423
+ ```python
424
+ # Create timestamped tarball using subshell date command:
425
+ $(tar -czf backup-$(date +%Y%m%d).tar.gz /var/data)
426
+
427
+ # Create git release tag from file content:
428
+ $(git tag release-$(cat VERSION))
429
+ ```
430
+
431
+ ---
432
+
433
+ ### 6. The `CommandResult` Object
434
+
435
+ Captured command expressions return a `CommandResult` instance with rich inspection capabilities:
436
+
437
+ | Attribute / Method | Type | Description |
438
+ |---|---|---|
439
+ | `res.stdout` | `str` | Full standard output |
440
+ | `res.stderr` | `str` | Full standard error |
441
+ | `res.exit_code` | `int` | Process exit status code (`0` = success) |
442
+ | `res.duration` | `float` | Command execution time in seconds |
443
+ | `res.command` | `str` | Exact command string executed |
444
+ | `res.lines` | `list[str]` | List of non-empty stdout lines |
445
+ | `res.text` | `str` | Trimmed standard output (`stdout.strip()`) |
446
+ | `res.json` | `DynamicObj` | Parsed JSON object / list |
447
+ | `for line in res:` | `Iterator[str]` | Iterate directly over lines in stdout |
448
+ | `res[index]` | `str` | Access a specific line by index |
449
+ | `bool(res)` | `bool` | **Truthiness**: `True` if `exit_code == 0`, else `False` |
450
+
451
+ #### Truthiness Example:
452
+ ```python
453
+ if $(git diff --quiet):
454
+ print("Working tree clean")
455
+ else:
456
+ print("Uncommitted changes detected")
457
+ ```
458
+
459
+ ---
460
+
461
+ ### 7. Built-in Context Managers
462
+
463
+ Every `.spy` script and module automatically has access to `cd()` and `env()` as first-class primitives without requiring any manual `import` statement.
464
+
465
+ #### `with cd(path)` — Directory Navigation
466
+ Changes current working directory for the duration of the `with` block and **guarantees** restoration to the previous directory upon exiting, even if an exception occurs:
467
+ ```python
468
+ # 1. Work in a specific subproject directory
469
+ with cd("services/billing"):
470
+ $(cargo build --release)!
471
+ $(cargo test)
472
+
473
+ # 2. Nested directory navigation
474
+ with cd("packages"):
475
+ with cd("frontend"):
476
+ $(npm test)
477
+ # Automatically back in "packages"
478
+ # Automatically back in the root directory
479
+ ```
480
+
481
+ #### `with env(**kwargs)` — Temporary Environment Variables
482
+ Sets or overrides environment variables for the duration of the `with` block and safely restores the original environment afterwards:
483
+ ```python
484
+ with env(AWS_DEFAULT_REGION="eu-west-1", STAGE="staging"):
485
+ $(aws s3 ls)
486
+ $(serverless deploy)
487
+ # AWS_DEFAULT_REGION and STAGE are restored to their original values
488
+ ```
489
+
490
+ #### Combining `cd()` and `env()`
491
+ You can combine multiple context managers cleanly on a single line:
492
+ ```python
493
+ with cd("apps/backend"), env(DATABASE_URL="postgres://test:5432/db", LOG_LEVEL="DEBUG"):
494
+ $(alembic upgrade head)!
495
+ $(pytest -v)
496
+ ```
497
+
498
+ ---
499
+
500
+ ### 8. Modular Architecture (`.spy` Imports)
501
+
502
+ You can structure large DevOps and infrastructure projects into modular files. `.spy` scripts can import other `.spy` scripts or packages natively:
503
+
504
+ ```python
505
+ # main.spy
506
+ import devops_utils
507
+ from infrastructure.cloud import deploy_cluster
508
+
509
+ status = devops_utils.get_git_status()
510
+ deploy_cluster("production")
511
+ ```
512
+
513
+ The underlying import hook compiles `.spy` files into standard Python bytecode on the fly with zero disk pollution.
514
+
515
+ ---
516
+
517
+ ### 9. Interactive REPL
518
+
519
+ `spy` includes a dedicated interactive read-eval-print loop with instant transpilation:
520
+
521
+ ```powershell
522
+ # Launch interactive shell
523
+ spy repl
524
+ # or simply
525
+ spy
526
+ ```
527
+
528
+ ```text
529
+ >>> branch = $(git branch --show-current).text
530
+ >>> branch
531
+ 'main'
532
+ >>> for file in $(git ls-files):
533
+ ... if file.endswith(".spy"):
534
+ ... print("Found spy script:", file)
535
+ ...
536
+ ```
537
+
538
+ ---
539
+
540
+ ## Examples
541
+
542
+ The repository includes runnable `.spy` examples in the `examples/` directory:
543
+
544
+ - [examples/demo.spy](examples/demo.spy): Basic overview demonstrating variable interpolation, list expansion, and status checking.
545
+ - [examples/syntax_reference.spy](examples/syntax_reference.spy): Comprehensive, executable reference covering every syntax construct and execution mode.
546
+ - [examples/advanced_features.spy](examples/advanced_features.spy): Practical demonstration of the 8 advanced productivity features (streaming, `.tee`, `.input(...)`, `cd()`/`env()`, background jobs `&`, safe mode `?`, quote semantics).
547
+ - [examples/parallel_async_jobs.spy](examples/parallel_async_jobs.spy): Concurrent process orchestration showing how to launch multiple background jobs with `&`, await them all with `wait_all(...)`, and coordinate async coroutines with `asyncio.gather(...)`.
548
+ - [examples/complex_devops.spy](examples/complex_devops.spy): Advanced pipeline orchestrator demonstrating Python dataclasses, object-oriented design, dynamic JSON parsing, strict mode error handling (`try/except CommandError`), and splat expansion.
549
+ - [examples/modular_demo.spy](examples/modular_demo.spy) & [examples/devops_utils.spy](examples/devops_utils.spy): Modular multi-file architecture demonstrating how a `.spy` file can seamlessly import reusable functions, classes, and shell workflows from another `.spy` file or package.
550
+
551
+ Run them directly with `spy`:
552
+ ```powershell
553
+ spy examples/demo.spy
554
+ spy examples/syntax_reference.spy
555
+ spy examples/advanced_features.spy
556
+ spy examples/parallel_async_jobs.spy
557
+ spy examples/complex_devops.spy
558
+ spy examples/modular_demo.spy
559
+ ```
560
+
561
+
562
+ ## Getting Started
563
+
564
+ ### Prerequisites
565
+
566
+ - Python `>= 3.12`
567
+ - `uv` package manager
568
+ - Supported Platforms: Linux, macOS, and Windows (all tested in CI)
569
+
570
+ ### Development Setup
571
+
572
+ ```powershell
573
+ # Install dependencies
574
+ uv sync
575
+
576
+ # Run tests
577
+ uv run pytest
578
+
579
+ # Run CLI help
580
+ uv run pycli --help
581
+ ```
582
+
583
+ ## Editor Support
584
+
585
+ Language extensions and syntax highlighting definitions are provided in the `editors/` directory:
586
+
587
+ - **Visual Studio Code & Antigravity IDE** ([editors/vscode](editors/vscode)): Full Python + embedded shell grammar, command delimiter highlighting, interpolation scoping, and snippets (`cmd`, `cmdvar`, `cmdjson`, `cmdstrict`, `cmdsplat`, `cmdif`).
588
+ - To install in VSCode:
589
+ ```powershell
590
+ Copy-Item -Recurse -Force "editors/vscode" "$env:USERPROFILE\.vscode\extensions\pycli-vscode"
591
+ ```
592
+ - To install in Antigravity IDE:
593
+ ```powershell
594
+ Copy-Item -Recurse -Force "editors/vscode" "$env:USERPROFILE\.antigravity-ide\extensions\pycli-vscode"
595
+ ```
596
+ - **Notepad++** ([editors/notepadplusplus](editors/notepadplusplus)): User Defined Language (UDL) definition for `.spy` files.
597
+ - To install locally:
598
+ ```powershell
599
+ Copy-Item -Force "editors/notepadplusplus/pycli.xml" "$env:APPDATA\Notepad++\userDefineLangs\"
600
+ ```
601
+