stata-cli 0.2.2__tar.gz → 0.4.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 (80) hide show
  1. {stata_cli-0.2.2 → stata_cli-0.4.0}/PKG-INFO +103 -6
  2. {stata_cli-0.2.2 → stata_cli-0.4.0}/README.md +101 -4
  3. {stata_cli-0.2.2 → stata_cli-0.4.0}/pyproject.toml +5 -2
  4. stata_cli-0.4.0/src/stata_cli/__init__.py +1 -0
  5. {stata_cli-0.2.2 → stata_cli-0.4.0}/src/stata_cli/daemon.py +31 -0
  6. {stata_cli-0.2.2 → stata_cli-0.4.0}/src/stata_cli/engine.py +181 -8
  7. {stata_cli-0.2.2 → stata_cli-0.4.0}/src/stata_cli/main.py +202 -5
  8. stata_cli-0.4.0/src/stata_cli/skill_registry.py +171 -0
  9. stata_cli-0.4.0/src/stata_cli/skills/overview.md +196 -0
  10. stata_cli-0.4.0/src/stata_cli/skills/packages/asdoc.md +357 -0
  11. stata_cli-0.4.0/src/stata_cli/skills/packages/binsreg.md +358 -0
  12. stata_cli-0.4.0/src/stata_cli/skills/packages/coefplot.md +397 -0
  13. stata_cli-0.4.0/src/stata_cli/skills/packages/data-manipulation.md +407 -0
  14. stata_cli-0.4.0/src/stata_cli/skills/packages/diagnostics.md +621 -0
  15. stata_cli-0.4.0/src/stata_cli/skills/packages/did.md +583 -0
  16. stata_cli-0.4.0/src/stata_cli/skills/packages/estout.md +676 -0
  17. stata_cli-0.4.0/src/stata_cli/skills/packages/event-study.md +1032 -0
  18. stata_cli-0.4.0/src/stata_cli/skills/packages/graph-schemes.md +633 -0
  19. stata_cli-0.4.0/src/stata_cli/skills/packages/ivreg2.md +387 -0
  20. stata_cli-0.4.0/src/stata_cli/skills/packages/nprobust.md +447 -0
  21. stata_cli-0.4.0/src/stata_cli/skills/packages/outreg2.md +424 -0
  22. stata_cli-0.4.0/src/stata_cli/skills/packages/package-management.md +319 -0
  23. stata_cli-0.4.0/src/stata_cli/skills/packages/psmatch2.md +658 -0
  24. stata_cli-0.4.0/src/stata_cli/skills/packages/rdrobust.md +498 -0
  25. stata_cli-0.4.0/src/stata_cli/skills/packages/reghdfe.md +372 -0
  26. stata_cli-0.4.0/src/stata_cli/skills/packages/synth.md +873 -0
  27. stata_cli-0.4.0/src/stata_cli/skills/packages/tabout.md +533 -0
  28. stata_cli-0.4.0/src/stata_cli/skills/packages/winsor.md +284 -0
  29. stata_cli-0.4.0/src/stata_cli/skills/packages/xtabond2.md +544 -0
  30. stata_cli-0.4.0/src/stata_cli/skills/references/advanced-programming.md +506 -0
  31. stata_cli-0.4.0/src/stata_cli/skills/references/basics-getting-started.md +237 -0
  32. stata_cli-0.4.0/src/stata_cli/skills/references/bootstrap-simulation.md +327 -0
  33. stata_cli-0.4.0/src/stata_cli/skills/references/data-import-export.md +282 -0
  34. stata_cli-0.4.0/src/stata_cli/skills/references/data-management.md +426 -0
  35. stata_cli-0.4.0/src/stata_cli/skills/references/date-time-functions.md +282 -0
  36. stata_cli-0.4.0/src/stata_cli/skills/references/descriptive-statistics.md +268 -0
  37. stata_cli-0.4.0/src/stata_cli/skills/references/difference-in-differences.md +750 -0
  38. stata_cli-0.4.0/src/stata_cli/skills/references/external-tools-integration.md +966 -0
  39. stata_cli-0.4.0/src/stata_cli/skills/references/gmm-estimation.md +367 -0
  40. stata_cli-0.4.0/src/stata_cli/skills/references/graphics.md +344 -0
  41. stata_cli-0.4.0/src/stata_cli/skills/references/limited-dependent-variables.md +289 -0
  42. stata_cli-0.4.0/src/stata_cli/skills/references/linear-regression.md +398 -0
  43. stata_cli-0.4.0/src/stata_cli/skills/references/machine-learning.md +511 -0
  44. stata_cli-0.4.0/src/stata_cli/skills/references/mata-data-access.md +370 -0
  45. stata_cli-0.4.0/src/stata_cli/skills/references/mata-introduction.md +313 -0
  46. stata_cli-0.4.0/src/stata_cli/skills/references/mata-matrix-operations.md +305 -0
  47. stata_cli-0.4.0/src/stata_cli/skills/references/mata-programming.md +400 -0
  48. stata_cli-0.4.0/src/stata_cli/skills/references/matching-methods.md +742 -0
  49. stata_cli-0.4.0/src/stata_cli/skills/references/mathematical-functions.md +269 -0
  50. stata_cli-0.4.0/src/stata_cli/skills/references/maximum-likelihood.md +749 -0
  51. stata_cli-0.4.0/src/stata_cli/skills/references/missing-data-handling.md +712 -0
  52. stata_cli-0.4.0/src/stata_cli/skills/references/nonparametric-methods.md +478 -0
  53. stata_cli-0.4.0/src/stata_cli/skills/references/panel-data.md +294 -0
  54. stata_cli-0.4.0/src/stata_cli/skills/references/programming-basics.md +440 -0
  55. stata_cli-0.4.0/src/stata_cli/skills/references/regression-discontinuity.md +486 -0
  56. stata_cli-0.4.0/src/stata_cli/skills/references/sample-selection.md +670 -0
  57. stata_cli-0.4.0/src/stata_cli/skills/references/sem-factor-analysis.md +576 -0
  58. stata_cli-0.4.0/src/stata_cli/skills/references/spatial-analysis.md +766 -0
  59. stata_cli-0.4.0/src/stata_cli/skills/references/string-functions.md +318 -0
  60. stata_cli-0.4.0/src/stata_cli/skills/references/survey-data-analysis.md +595 -0
  61. stata_cli-0.4.0/src/stata_cli/skills/references/survival-analysis.md +466 -0
  62. stata_cli-0.4.0/src/stata_cli/skills/references/tables-reporting.md +973 -0
  63. stata_cli-0.4.0/src/stata_cli/skills/references/time-series.md +345 -0
  64. stata_cli-0.4.0/src/stata_cli/skills/references/treatment-effects.md +804 -0
  65. stata_cli-0.4.0/src/stata_cli/skills/references/variables-operators.md +206 -0
  66. stata_cli-0.4.0/src/stata_cli/skills/references/workflow-best-practices.md +1176 -0
  67. {stata_cli-0.2.2 → stata_cli-0.4.0}/src/stata_cli/utils.py +1 -0
  68. {stata_cli-0.2.2 → stata_cli-0.4.0}/src/stata_cli.egg-info/PKG-INFO +103 -6
  69. stata_cli-0.4.0/src/stata_cli.egg-info/SOURCES.txt +76 -0
  70. stata_cli-0.2.2/src/stata_cli/__init__.py +0 -1
  71. stata_cli-0.2.2/src/stata_cli.egg-info/SOURCES.txt +0 -17
  72. {stata_cli-0.2.2 → stata_cli-0.4.0}/setup.cfg +0 -0
  73. {stata_cli-0.2.2 → stata_cli-0.4.0}/src/stata_cli/__main__.py +0 -0
  74. {stata_cli-0.2.2 → stata_cli-0.4.0}/src/stata_cli/graph_artifacts.py +0 -0
  75. {stata_cli-0.2.2 → stata_cli-0.4.0}/src/stata_cli/output_filter.py +0 -0
  76. {stata_cli-0.2.2 → stata_cli-0.4.0}/src/stata_cli/smcl_parser.py +0 -0
  77. {stata_cli-0.2.2 → stata_cli-0.4.0}/src/stata_cli.egg-info/dependency_links.txt +0 -0
  78. {stata_cli-0.2.2 → stata_cli-0.4.0}/src/stata_cli.egg-info/entry_points.txt +0 -0
  79. {stata_cli-0.2.2 → stata_cli-0.4.0}/src/stata_cli.egg-info/requires.txt +0 -0
  80. {stata_cli-0.2.2 → stata_cli-0.4.0}/src/stata_cli.egg-info/top_level.txt +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stata-cli
3
- Version: 0.2.2
3
+ Version: 0.4.0
4
4
  Summary: Command-line interface for running Stata commands via PyStata
5
5
  License: MIT
6
6
  Keywords: stata,cli,statistics,data-science
7
- Requires-Python: >=3.10
7
+ Requires-Python: >=3.9
8
8
  Description-Content-Type: text/markdown
9
9
  Requires-Dist: click>=8.0
10
10
  Provides-Extra: data
@@ -13,8 +13,12 @@ Requires-Dist: pandas; extra == "data"
13
13
 
14
14
  # stata-cli
15
15
 
16
+ > **Stata CLI Is All Reg Monkeys Need**
17
+
18
+ ![stata-cli banner](assets/banner.png)
19
+
16
20
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
17
- [![Python Version](https://img.shields.io/badge/python-%3E%3D3.10-blue.svg)](https://www.python.org/)
21
+ [![Python Version](https://img.shields.io/badge/python-%3E%3D3.9-blue.svg)](https://www.python.org/)
18
22
  [![npm version](https://img.shields.io/npm/v/stata-cli.svg)](https://www.npmjs.com/package/stata-cli)
19
23
 
20
24
  [中文版](README.zh.md) | [English](README.md)
@@ -39,18 +43,25 @@ A command-line interface for [Stata](https://www.stata.com/) via PyStata — bui
39
43
  | **Run Code** | Execute inline Stata code, multi-line blocks, or pipe from stdin |
40
44
  | **Do Files** | Run `.do` files with `///` line continuation support and graph auto-naming |
41
45
  | **Data Viewer** | View current dataset as JSON with `if`-condition filtering and row limits |
46
+ | **Variable Metadata** | Inspect variable names, types, formats, and labels via `vars` |
47
+ | **Stored Results** | Retrieve r(), e(), s() results as structured JSON via `return` |
48
+ | **Matrix Access** | Read Stata matrices (e.g. `e(b)`, `e(V)`) as JSON via `matrix` |
49
+ | **Value Labels** | List and inspect value labels via `labels` |
50
+ | **Macro Access** | Get/set Stata macros including `c()`, `e()`, `r()` system macros |
51
+ | **Frame Management** | List Stata frames and current working frame via `frame` |
42
52
  | **Help System** | Browse Stata help topics with SMCL-to-plain-text conversion |
43
- | **Graph Export** | Auto-detect and export graphs as PNG to `~/.stata-cli/graphs/` |
53
+ | **Graph Export** | Auto-detect and export graphs as PNG/SVG/PDF to `~/.stata-cli/graphs/` |
44
54
  | **Daemon Mode** | Persistent background process for sub-second execution via Unix socket |
45
- | **Output Control** | Compact mode, JSON output, token limit management with full-output file save |
55
+ | **Output Control** | Compact mode, JSON output, token limit management, log file output |
46
56
  | **Interruption** | Send break signal to stop long-running commands |
57
+ | **Skill Library** | Built-in Stata reference with 57 topics: syntax, econometrics, causal inference, packages |
47
58
 
48
59
  ## Installation & Quick Start
49
60
 
50
61
  ### Requirements
51
62
 
52
63
  - **Stata 17+** installed on your machine (provides the PyStata library)
53
- - Python 3.10+
64
+ - Python 3.9+
54
65
 
55
66
  ### Quick Start (Human Users)
56
67
 
@@ -197,6 +208,72 @@ stata-cli detect
197
208
 
198
209
  Prints the auto-detected Stata installation path.
199
210
 
211
+ ### `return` — Retrieve Stored Results
212
+
213
+ ```bash
214
+ stata-cli return r # r() results (after summarize, etc.)
215
+ stata-cli return e # e() results (after regress, etc.)
216
+ stata-cli return s # s() results
217
+ ```
218
+
219
+ Returns r(), e(), or s() stored results as structured JSON — scalars, macros, and matrix references.
220
+
221
+ ### `vars` — Variable Metadata
222
+
223
+ ```bash
224
+ stata-cli vars # all variables
225
+ stata-cli vars price mpg # specific variables
226
+ ```
227
+
228
+ Returns variable names, types, formats, and labels as JSON. More structured than `describe`.
229
+
230
+ ### `matrix` — Read Stata Matrices
231
+
232
+ ```bash
233
+ stata-cli matrix e(b) # coefficient vector
234
+ stata-cli matrix e(V) # variance-covariance matrix
235
+ ```
236
+
237
+ Returns matrix data, dimensions, and row/column names as JSON.
238
+
239
+ ### `labels` — Value Labels
240
+
241
+ ```bash
242
+ stata-cli labels # list all value label names
243
+ stata-cli labels origin # show value-label mapping
244
+ stata-cli labels --var foreign # show label attached to a variable
245
+ ```
246
+
247
+ ### `macro` — Get/Set Macros
248
+
249
+ ```bash
250
+ stata-cli macro get "c(current_date)"
251
+ stata-cli macro get "e(cmd)"
252
+ stata-cli macro set myvar "hello"
253
+ ```
254
+
255
+ Access Stata macros including system macros (`c()`, `e()`, `r()`).
256
+
257
+ ### `frame` — List Frames
258
+
259
+ ```bash
260
+ stata-cli frame
261
+ ```
262
+
263
+ Shows all Stata frames and the current working frame.
264
+
265
+ ### `skill` — Stata Reference Library
266
+
267
+ ```bash
268
+ stata-cli skill # overview: gotchas, patterns, topic routing table
269
+ stata-cli skill --list # list all 57 topics with descriptions
270
+ stata-cli skill regression # linear regression reference
271
+ stata-cli skill did # difference-in-differences guide
272
+ stata-cli skill reghdfe # reghdfe package guide
273
+ ```
274
+
275
+ Built-in reference library covering data management, econometrics, causal inference, graphics, Mata programming, and 20+ community packages. Aliases supported (e.g. `did` for `difference-in-differences`, `panel` for `panel-data`).
276
+
200
277
  ## Daemon Mode
201
278
 
202
279
  The daemon keeps PyStata alive in the background — reduces execution time from **~2-3s to ~85ms** (35x speedup).
@@ -234,6 +311,8 @@ The daemon auto-shuts down after 1 hour of inactivity (configurable with `--idle
234
311
  | `--max-tokens N` | Max output tokens (0=unlimited) | 0 |
235
312
  | `--no-daemon` | Force direct execution | off |
236
313
  | `--graphs-dir PATH` | Graph export directory | `~/.stata-cli/graphs/` |
314
+ | `--graph-format [png\|svg\|pdf]` | Graph export format | `png` |
315
+ | `--log PATH` | Save output to a log file | off |
237
316
 
238
317
  ### JSON Output
239
318
 
@@ -313,6 +392,21 @@ regress price mpg weight
313
392
  predict yhat
314
393
  list make price yhat in 1/5"
315
394
 
395
+ # Retrieve regression results as structured JSON
396
+ stata-cli return e
397
+
398
+ # Get coefficient matrix
399
+ stata-cli matrix e(b)
400
+
401
+ # Inspect variable metadata
402
+ stata-cli vars price mpg weight
403
+
404
+ # Check value labels
405
+ stata-cli labels --var foreign
406
+
407
+ # Read system macros
408
+ stata-cli macro get "c(N)"
409
+
316
410
  # Check data after loading
317
411
  stata-cli data --if "price>10000"
318
412
 
@@ -325,6 +419,9 @@ describe"
325
419
 
326
420
  # JSON mode for structured parsing
327
421
  stata-cli --json run "display 1+1"
422
+
423
+ # Export graph as SVG
424
+ stata-cli --graph-format svg run "scatter price mpg"
328
425
  ```
329
426
 
330
427
  ## Contributing
@@ -1,7 +1,11 @@
1
1
  # stata-cli
2
2
 
3
+ > **Stata CLI Is All Reg Monkeys Need**
4
+
5
+ ![stata-cli banner](assets/banner.png)
6
+
3
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
- [![Python Version](https://img.shields.io/badge/python-%3E%3D3.10-blue.svg)](https://www.python.org/)
8
+ [![Python Version](https://img.shields.io/badge/python-%3E%3D3.9-blue.svg)](https://www.python.org/)
5
9
  [![npm version](https://img.shields.io/npm/v/stata-cli.svg)](https://www.npmjs.com/package/stata-cli)
6
10
 
7
11
  [中文版](README.zh.md) | [English](README.md)
@@ -26,18 +30,25 @@ A command-line interface for [Stata](https://www.stata.com/) via PyStata — bui
26
30
  | **Run Code** | Execute inline Stata code, multi-line blocks, or pipe from stdin |
27
31
  | **Do Files** | Run `.do` files with `///` line continuation support and graph auto-naming |
28
32
  | **Data Viewer** | View current dataset as JSON with `if`-condition filtering and row limits |
33
+ | **Variable Metadata** | Inspect variable names, types, formats, and labels via `vars` |
34
+ | **Stored Results** | Retrieve r(), e(), s() results as structured JSON via `return` |
35
+ | **Matrix Access** | Read Stata matrices (e.g. `e(b)`, `e(V)`) as JSON via `matrix` |
36
+ | **Value Labels** | List and inspect value labels via `labels` |
37
+ | **Macro Access** | Get/set Stata macros including `c()`, `e()`, `r()` system macros |
38
+ | **Frame Management** | List Stata frames and current working frame via `frame` |
29
39
  | **Help System** | Browse Stata help topics with SMCL-to-plain-text conversion |
30
- | **Graph Export** | Auto-detect and export graphs as PNG to `~/.stata-cli/graphs/` |
40
+ | **Graph Export** | Auto-detect and export graphs as PNG/SVG/PDF to `~/.stata-cli/graphs/` |
31
41
  | **Daemon Mode** | Persistent background process for sub-second execution via Unix socket |
32
- | **Output Control** | Compact mode, JSON output, token limit management with full-output file save |
42
+ | **Output Control** | Compact mode, JSON output, token limit management, log file output |
33
43
  | **Interruption** | Send break signal to stop long-running commands |
44
+ | **Skill Library** | Built-in Stata reference with 57 topics: syntax, econometrics, causal inference, packages |
34
45
 
35
46
  ## Installation & Quick Start
36
47
 
37
48
  ### Requirements
38
49
 
39
50
  - **Stata 17+** installed on your machine (provides the PyStata library)
40
- - Python 3.10+
51
+ - Python 3.9+
41
52
 
42
53
  ### Quick Start (Human Users)
43
54
 
@@ -184,6 +195,72 @@ stata-cli detect
184
195
 
185
196
  Prints the auto-detected Stata installation path.
186
197
 
198
+ ### `return` — Retrieve Stored Results
199
+
200
+ ```bash
201
+ stata-cli return r # r() results (after summarize, etc.)
202
+ stata-cli return e # e() results (after regress, etc.)
203
+ stata-cli return s # s() results
204
+ ```
205
+
206
+ Returns r(), e(), or s() stored results as structured JSON — scalars, macros, and matrix references.
207
+
208
+ ### `vars` — Variable Metadata
209
+
210
+ ```bash
211
+ stata-cli vars # all variables
212
+ stata-cli vars price mpg # specific variables
213
+ ```
214
+
215
+ Returns variable names, types, formats, and labels as JSON. More structured than `describe`.
216
+
217
+ ### `matrix` — Read Stata Matrices
218
+
219
+ ```bash
220
+ stata-cli matrix e(b) # coefficient vector
221
+ stata-cli matrix e(V) # variance-covariance matrix
222
+ ```
223
+
224
+ Returns matrix data, dimensions, and row/column names as JSON.
225
+
226
+ ### `labels` — Value Labels
227
+
228
+ ```bash
229
+ stata-cli labels # list all value label names
230
+ stata-cli labels origin # show value-label mapping
231
+ stata-cli labels --var foreign # show label attached to a variable
232
+ ```
233
+
234
+ ### `macro` — Get/Set Macros
235
+
236
+ ```bash
237
+ stata-cli macro get "c(current_date)"
238
+ stata-cli macro get "e(cmd)"
239
+ stata-cli macro set myvar "hello"
240
+ ```
241
+
242
+ Access Stata macros including system macros (`c()`, `e()`, `r()`).
243
+
244
+ ### `frame` — List Frames
245
+
246
+ ```bash
247
+ stata-cli frame
248
+ ```
249
+
250
+ Shows all Stata frames and the current working frame.
251
+
252
+ ### `skill` — Stata Reference Library
253
+
254
+ ```bash
255
+ stata-cli skill # overview: gotchas, patterns, topic routing table
256
+ stata-cli skill --list # list all 57 topics with descriptions
257
+ stata-cli skill regression # linear regression reference
258
+ stata-cli skill did # difference-in-differences guide
259
+ stata-cli skill reghdfe # reghdfe package guide
260
+ ```
261
+
262
+ Built-in reference library covering data management, econometrics, causal inference, graphics, Mata programming, and 20+ community packages. Aliases supported (e.g. `did` for `difference-in-differences`, `panel` for `panel-data`).
263
+
187
264
  ## Daemon Mode
188
265
 
189
266
  The daemon keeps PyStata alive in the background — reduces execution time from **~2-3s to ~85ms** (35x speedup).
@@ -221,6 +298,8 @@ The daemon auto-shuts down after 1 hour of inactivity (configurable with `--idle
221
298
  | `--max-tokens N` | Max output tokens (0=unlimited) | 0 |
222
299
  | `--no-daemon` | Force direct execution | off |
223
300
  | `--graphs-dir PATH` | Graph export directory | `~/.stata-cli/graphs/` |
301
+ | `--graph-format [png\|svg\|pdf]` | Graph export format | `png` |
302
+ | `--log PATH` | Save output to a log file | off |
224
303
 
225
304
  ### JSON Output
226
305
 
@@ -300,6 +379,21 @@ regress price mpg weight
300
379
  predict yhat
301
380
  list make price yhat in 1/5"
302
381
 
382
+ # Retrieve regression results as structured JSON
383
+ stata-cli return e
384
+
385
+ # Get coefficient matrix
386
+ stata-cli matrix e(b)
387
+
388
+ # Inspect variable metadata
389
+ stata-cli vars price mpg weight
390
+
391
+ # Check value labels
392
+ stata-cli labels --var foreign
393
+
394
+ # Read system macros
395
+ stata-cli macro get "c(N)"
396
+
303
397
  # Check data after loading
304
398
  stata-cli data --if "price>10000"
305
399
 
@@ -312,6 +406,9 @@ describe"
312
406
 
313
407
  # JSON mode for structured parsing
314
408
  stata-cli --json run "display 1+1"
409
+
410
+ # Export graph as SVG
411
+ stata-cli --graph-format svg run "scatter price mpg"
315
412
  ```
316
413
 
317
414
  ## Contributing
@@ -1,9 +1,9 @@
1
1
  [project]
2
2
  name = "stata-cli"
3
- version = "0.2.2"
3
+ version = "0.4.0"
4
4
  description = "Command-line interface for running Stata commands via PyStata"
5
5
  readme = "README.md"
6
- requires-python = ">=3.10"
6
+ requires-python = ">=3.9"
7
7
  license = {text = "MIT"}
8
8
  keywords = ["stata", "cli", "statistics", "data-science"]
9
9
 
@@ -23,3 +23,6 @@ build-backend = "setuptools.build_meta"
23
23
 
24
24
  [tool.setuptools.packages.find]
25
25
  where = ["src"]
26
+
27
+ [tool.setuptools.package-data]
28
+ stata_cli = ["skills/**/*.md"]
@@ -0,0 +1 @@
1
+ __version__ = "0.4.0"
@@ -162,6 +162,37 @@ class DaemonServer:
162
162
  ok = self._engine.stop()
163
163
  _send_msg(conn, {"status": "ok" if ok else "no_op"})
164
164
 
165
+ elif cmd_type == "get_return":
166
+ data = self._engine.get_return(rtype=payload.get("rtype", "r"))
167
+ _send_msg(conn, data)
168
+
169
+ elif cmd_type == "get_vars":
170
+ data = self._engine.get_vars()
171
+ _send_msg(conn, data)
172
+
173
+ elif cmd_type == "get_matrix":
174
+ data = self._engine.get_matrix(payload.get("name", ""))
175
+ _send_msg(conn, data)
176
+
177
+ elif cmd_type == "get_labels":
178
+ data = self._engine.get_labels(
179
+ name=payload.get("name"),
180
+ var=payload.get("var"),
181
+ )
182
+ _send_msg(conn, data)
183
+
184
+ elif cmd_type == "get_macro":
185
+ data = self._engine.get_macro(payload.get("name", ""))
186
+ _send_msg(conn, data)
187
+
188
+ elif cmd_type == "set_macro":
189
+ data = self._engine.set_macro(payload.get("name", ""), payload.get("value", ""))
190
+ _send_msg(conn, data)
191
+
192
+ elif cmd_type == "get_frames":
193
+ data = self._engine.get_frames()
194
+ _send_msg(conn, data)
195
+
165
196
  elif cmd_type == "status":
166
197
  _send_msg(conn, {
167
198
  "status": "ok",
@@ -50,14 +50,18 @@ _EXISTING_GRAPHN_RE = re.compile(r"\bname\s*\(\s*graph(\d+)", re.IGNORECASE)
50
50
  class StataEngine:
51
51
  """Thin wrapper around PyStata for single-process command execution."""
52
52
 
53
- def __init__(self, stata_path: str, edition: str = "mp", graphs_dir: Optional[str] = None):
53
+ def __init__(self, stata_path: str, edition: str = "mp", graphs_dir: Optional[str] = None, graph_format: str = "png"):
54
54
  self.stata_path = stata_path
55
55
  self.edition = edition.lower()
56
56
  self.graphs_dir = graphs_dir or get_graphs_root()
57
+ self.graph_format = graph_format.lower()
57
58
  self._stata = None
58
59
  self._stlib = None
59
60
  self._initialized = False
60
61
  self._stop_sent = False
62
+ self._last_r: dict = {}
63
+ self._last_e: dict = {}
64
+ self._last_s: dict = {}
61
65
 
62
66
  def _ensure_initialized(self) -> None:
63
67
  if self._initialized:
@@ -89,6 +93,13 @@ class StataEngine:
89
93
 
90
94
  self._stata = stata_module
91
95
 
96
+ if self.graph_format != "png":
97
+ try:
98
+ from pystata import config as pystata_config # type: ignore[import-untyped]
99
+ pystata_config.set_graph_format(self.graph_format)
100
+ except Exception:
101
+ pass
102
+
92
103
  try:
93
104
  from pystata.config import stlib as stlib_module # type: ignore[import-untyped]
94
105
  self._stlib = stlib_module
@@ -113,19 +124,27 @@ class StataEngine:
113
124
  )
114
125
  log_file_stata = log_file.replace("\\", "/")
115
126
 
116
- wrapped = (
127
+ setup = (
117
128
  f'capture log close _all\n'
118
129
  f'log using "{log_file_stata}", replace text\n'
119
- f'{code}\n'
120
- f'capture log close _all\n'
121
130
  )
131
+ teardown = 'capture log close _all\n'
122
132
 
123
133
  start = time.time()
124
134
  try:
125
135
  old_stdout = sys.stdout
126
136
  sys.stdout = io.StringIO()
127
137
  try:
128
- self._stata.run(wrapped, echo=True, inline=False)
138
+ self._stata.run(setup + code, echo=True, inline=False)
139
+ finally:
140
+ sys.stdout = old_stdout
141
+
142
+ self._capture_stored_results()
143
+
144
+ old_stdout = sys.stdout
145
+ sys.stdout = io.StringIO()
146
+ try:
147
+ self._stata.run(teardown, echo=False, inline=False)
129
148
  finally:
130
149
  captured_stdout = sys.stdout.getvalue()
131
150
  sys.stdout = old_stdout
@@ -300,6 +319,119 @@ class StataEngine:
300
319
 
301
320
  return result
302
321
 
322
+ def get_return(self, rtype: str = "r") -> Dict[str, Any]:
323
+ """Retrieve stored results: r(), e(), or s()."""
324
+ self._ensure_initialized()
325
+ try:
326
+ if rtype == "r":
327
+ return {"status": "success", "type": "r", "results": dict(self._last_r)}
328
+ elif rtype == "e":
329
+ return {"status": "success", "type": "e", "results": dict(self._last_e)}
330
+ elif rtype == "s":
331
+ return {"status": "success", "type": "s", "results": dict(self._last_s)}
332
+ else:
333
+ return {"status": "error", "error": f"Unknown return type: {rtype}"}
334
+ except Exception as exc:
335
+ return {"status": "error", "error": str(exc)}
336
+
337
+ def get_vars(self) -> Dict[str, Any]:
338
+ """Return variable metadata for the current dataset."""
339
+ self._ensure_initialized()
340
+ try:
341
+ import sfi # type: ignore[import-untyped]
342
+ nvar = sfi.Data.getVarCount()
343
+ nobs = sfi.Data.getObsTotal()
344
+ variables = []
345
+ for i in range(nvar):
346
+ name = sfi.Data.getVarName(i)
347
+ variables.append({
348
+ "name": name,
349
+ "type": sfi.Data.getVarType(i),
350
+ "format": sfi.Data.getVarFormat(i),
351
+ "label": sfi.Data.getVarLabel(i),
352
+ "is_string": sfi.Data.isVarTypeStr(i),
353
+ })
354
+ return {
355
+ "status": "success",
356
+ "n_vars": nvar,
357
+ "n_obs": nobs,
358
+ "variables": variables,
359
+ }
360
+ except Exception as exc:
361
+ return {"status": "error", "error": str(exc)}
362
+
363
+ def get_matrix(self, name: str) -> Dict[str, Any]:
364
+ """Return a Stata matrix as a dict."""
365
+ self._ensure_initialized()
366
+ try:
367
+ import sfi # type: ignore[import-untyped]
368
+ nrows = sfi.Matrix.getRowTotal(name)
369
+ ncols = sfi.Matrix.getColTotal(name)
370
+ row_names = sfi.Matrix.getRowNames(name)
371
+ col_names = sfi.Matrix.getColNames(name)
372
+ data = sfi.Matrix.get(name)
373
+ return {
374
+ "status": "success",
375
+ "name": name,
376
+ "rows": nrows,
377
+ "cols": ncols,
378
+ "row_names": row_names,
379
+ "col_names": col_names,
380
+ "data": data,
381
+ }
382
+ except Exception as exc:
383
+ return {"status": "error", "error": str(exc)}
384
+
385
+ def get_labels(self, name: Optional[str] = None, var: Optional[str] = None) -> Dict[str, Any]:
386
+ """Return value labels."""
387
+ self._ensure_initialized()
388
+ try:
389
+ import sfi # type: ignore[import-untyped]
390
+ if var:
391
+ label_name = sfi.ValueLabel.getVarValueLabel(var)
392
+ if not label_name:
393
+ return {"status": "success", "variable": var, "label_name": "", "labels": {}}
394
+ mapping = sfi.ValueLabel.getValueLabels(label_name)
395
+ return {"status": "success", "variable": var, "label_name": label_name, "labels": mapping}
396
+ if name:
397
+ mapping = sfi.ValueLabel.getValueLabels(name)
398
+ return {"status": "success", "name": name, "labels": mapping}
399
+ names = sfi.ValueLabel.getNames()
400
+ return {"status": "success", "names": names}
401
+ except Exception as exc:
402
+ return {"status": "error", "error": str(exc)}
403
+
404
+ def get_macro(self, name: str) -> Dict[str, Any]:
405
+ """Get the value of a Stata macro."""
406
+ self._ensure_initialized()
407
+ try:
408
+ import sfi # type: ignore[import-untyped]
409
+ value = sfi.Macro.getGlobal(name)
410
+ return {"status": "success", "name": name, "value": value}
411
+ except Exception as exc:
412
+ return {"status": "error", "error": str(exc)}
413
+
414
+ def set_macro(self, name: str, value: str) -> Dict[str, Any]:
415
+ """Set a Stata global macro."""
416
+ self._ensure_initialized()
417
+ try:
418
+ import sfi # type: ignore[import-untyped]
419
+ sfi.Macro.setGlobal(name, value)
420
+ return {"status": "success", "name": name, "value": value}
421
+ except Exception as exc:
422
+ return {"status": "error", "error": str(exc)}
423
+
424
+ def get_frames(self) -> Dict[str, Any]:
425
+ """Return list of Stata frames and the current working frame."""
426
+ self._ensure_initialized()
427
+ try:
428
+ import sfi # type: ignore[import-untyped]
429
+ frames = sfi.Frame.getFrames()
430
+ cwf = sfi.Frame.getCWF()
431
+ return {"status": "success", "frames": frames, "current": cwf}
432
+ except Exception as exc:
433
+ return {"status": "error", "error": str(exc)}
434
+
303
435
  def stop(self) -> bool:
304
436
  """Interrupt a running Stata command. Returns True if signal sent."""
305
437
  if self._stop_sent or self._stlib is None:
@@ -316,6 +448,44 @@ class StataEngine:
316
448
 
317
449
  # ── graph detection ──────────────────────────────────────────────────
318
450
 
451
+ def _capture_stored_results(self) -> None:
452
+ """Snapshot r(), e(), s() results via sfi before log close clears them."""
453
+ try:
454
+ import sfi # type: ignore[import-untyped]
455
+ except ImportError:
456
+ return
457
+
458
+ for rtype, store in [("r", "_last_r"), ("e", "_last_e"), ("s", "_last_s")]:
459
+ result: Dict[str, Any] = {}
460
+ cat = f"{rtype}()"
461
+ try:
462
+ scalar_names = sfi.SFIToolkit.listReturn(cat, "scalar")
463
+ if scalar_names and scalar_names.strip():
464
+ for name in scalar_names.strip().split():
465
+ try:
466
+ result[name] = sfi.Scalar.getValue(f"{rtype}({name})")
467
+ except Exception:
468
+ pass
469
+
470
+ macro_names = sfi.SFIToolkit.listReturn(cat, "macro")
471
+ if macro_names and macro_names.strip():
472
+ for name in macro_names.strip().split():
473
+ try:
474
+ result[name] = sfi.Macro.getGlobal(f"{rtype}({name})")
475
+ except Exception:
476
+ pass
477
+
478
+ if rtype != "s":
479
+ matrix_names = sfi.SFIToolkit.listReturn(cat, "matrix")
480
+ if matrix_names and matrix_names.strip():
481
+ for name in matrix_names.strip().split():
482
+ result[f"matrix:{name}"] = f"[matrix, use 'stata-cli matrix {rtype}({name})']"
483
+ except Exception:
484
+ pass
485
+ setattr(self, store, result)
486
+
487
+ # ── graph detection (continued) ─────────────────────────────────────
488
+
319
489
  def _reset_graph_tracking(self) -> None:
320
490
  if self._stlib is None:
321
491
  return
@@ -348,12 +518,15 @@ class StataEngine:
348
518
  self._stlib.StataSO_Execute(
349
519
  get_encode_str(f"quietly graph display {gname}"), False
350
520
  )
351
- graph_file = os.path.join(batch["batch_dir"], f"{gname}.png")
521
+ ext = self.graph_format
522
+ graph_file = os.path.join(batch["batch_dir"], f"{gname}.{ext}")
352
523
  graph_file_stata = graph_file.replace("\\", "/")
524
+ fmt_opt = f"as({ext}) " if ext != "png" else ""
525
+ size_opt = "width(800) height(600)" if ext == "png" else ""
353
526
  export_cmd = (
354
527
  f'quietly graph export "{graph_file_stata}", '
355
- f"name({gname}) replace width(800) height(600)"
356
- )
528
+ f"{fmt_opt}name({gname}) replace {size_opt}"
529
+ ).rstrip()
357
530
  rc = self._stlib.StataSO_Execute(get_encode_str(export_cmd), False)
358
531
  if rc != 0:
359
532
  continue