jstdata 0.2.0__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.
- jstdata/__init__.py +13 -0
- jstdata/agent_guide.py +530 -0
- jstdata/cli.py +682 -0
- jstdata/client.py +543 -0
- jstdata/models.py +161 -0
- jstdata/session.py +251 -0
- jstdata/utils.py +103 -0
- jstdata/workflows/__init__.py +88 -0
- jstdata/workflows/base.py +356 -0
- jstdata/workflows/bundled/__init__.py +1 -0
- jstdata/workflows/bundled/tutorial.yaml +14 -0
- jstdata/workflows/bundled/tutorial_script.yaml +45 -0
- jstdata/workflows/chart.py +357 -0
- jstdata/workflows/console.py +560 -0
- jstdata/workflows/discover.py +502 -0
- jstdata/workflows/export.py +171 -0
- jstdata/workflows/find.py +386 -0
- jstdata/workflows/host.py +255 -0
- jstdata/workflows/rank.py +658 -0
- jstdata/workflows/session_manager.py +277 -0
- jstdata/workflows/store.py +308 -0
- jstdata/workflows/tutorial.py +163 -0
- jstdata/workflows/tutorial_host.py +178 -0
- jstdata-0.2.0.dist-info/METADATA +227 -0
- jstdata-0.2.0.dist-info/RECORD +28 -0
- jstdata-0.2.0.dist-info/WHEEL +4 -0
- jstdata-0.2.0.dist-info/entry_points.txt +3 -0
- jstdata-0.2.0.dist-info/licenses/LICENSE +21 -0
jstdata/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .client import JSTDataClient
|
|
2
|
+
from .models import Entity, Metric, Series, Observation, Taxonomy
|
|
3
|
+
from .session import Session
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"JSTDataClient",
|
|
7
|
+
"Entity",
|
|
8
|
+
"Metric",
|
|
9
|
+
"Series",
|
|
10
|
+
"Observation",
|
|
11
|
+
"Taxonomy",
|
|
12
|
+
"Session",
|
|
13
|
+
]
|
jstdata/agent_guide.py
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
"""Generate the markdown agent bootstrap guide (`jst agent-guide`).
|
|
2
|
+
|
|
3
|
+
CLI commands, interactive steps, and session field names are derived from the
|
|
4
|
+
live Click tree, step registry, and ``Session`` dataclass. Conceptual glue
|
|
5
|
+
(policy, modes, recipes, data model, sessions) is static prose that changes rarely.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from dataclasses import fields
|
|
12
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
13
|
+
from typing import Iterable, get_args, get_origin
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
|
|
17
|
+
from .session import Session
|
|
18
|
+
from .workflows import list_steps
|
|
19
|
+
|
|
20
|
+
_PACKAGE = "jstdata"
|
|
21
|
+
|
|
22
|
+
# Session fields that expand the action space without helping typical agent work.
|
|
23
|
+
_SESSION_FIELDS_OMIT = frozenset(
|
|
24
|
+
{"start_date", "end_date", "start_time", "end_time"}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
_HARD_RULES = """\
|
|
28
|
+
## Hard rules
|
|
29
|
+
|
|
30
|
+
- **Never invent IDs.** Never invent metric, entity, taxonomy, or series ids from
|
|
31
|
+
English names. Resolve via search (or scoped list) first.
|
|
32
|
+
- **Resolve before use.** Resolve ids before `jst query`, session JSON, or workflow args.
|
|
33
|
+
- **Use `--format json`** on resource and search commands unless the user needs a table.
|
|
34
|
+
- **Do not launch interactive TUIs** (`jst run`, `jst workflow run`) unless the user
|
|
35
|
+
explicitly asks you to operate the UI. Prepare a Session/workflow and give them the
|
|
36
|
+
launch command.
|
|
37
|
+
- **Do not paginate exhaustively.** During discovery, use `--limit` 20–50. Do not walk
|
|
38
|
+
`offset` through the catalog unless the returned set is clearly insufficient.
|
|
39
|
+
- **Do not enumerate** `jst metric ls`, `jst series ls`, or full taxonomy populations
|
|
40
|
+
unless the task requires enumeration. Prefer `search` with a query (and taxonomy /
|
|
41
|
+
relation filters when relevant).
|
|
42
|
+
- **Do not retrieve deep history** until the candidate universe is narrowed. Use
|
|
43
|
+
`tail=1` (or similarly shallow queries) for triage.
|
|
44
|
+
- **Do not create a workflow** when direct CLI analysis satisfies the request. Use
|
|
45
|
+
workflows for human handoff of an interactive investigation.
|
|
46
|
+
- **Stop when good enough.** Prefer a partial useful result over open-ended search.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
_OPERATING_POLICY = """\
|
|
50
|
+
## Default operating policy
|
|
51
|
+
|
|
52
|
+
When solving a JST research task:
|
|
53
|
+
|
|
54
|
+
1. **Use metadata before observations.** Resolve entities, metrics, and taxonomies
|
|
55
|
+
before querying values.
|
|
56
|
+
2. **Start narrow.** Use the user's literal terminology first, then a small number of
|
|
57
|
+
obvious synonyms.
|
|
58
|
+
3. **Prefer bounded calls.** Small result limits during discovery; shallow `tail` /
|
|
59
|
+
`head` when values are needed only to triage.
|
|
60
|
+
4. **Prefer good-enough candidate sets** over exhaustive search unless the user asks
|
|
61
|
+
for comprehensiveness.
|
|
62
|
+
5. **Escalate to deep history only after narrowing** (specific series ids, date bounds).
|
|
63
|
+
6. **Hand off TUIs to the human.** Prepare Session JSON and/or a saved workflow; print
|
|
64
|
+
the `jst workflow run … --session …` (or `jst run …`) command for them.
|
|
65
|
+
7. **Prefer JSON** for anything you will parse or reason over.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
_OPERATING_MODES = """\
|
|
69
|
+
## Choose an operating mode
|
|
70
|
+
|
|
71
|
+
### Catalog discovery
|
|
72
|
+
|
|
73
|
+
Example: “Find metrics relevant to defense spending.”
|
|
74
|
+
|
|
75
|
+
Use search / show. Do **not** retrieve observations unless needed to disambiguate
|
|
76
|
+
candidates. Select a useful set and stop.
|
|
77
|
+
|
|
78
|
+
### Agent analysis
|
|
79
|
+
|
|
80
|
+
Example: “Compare military expenditure as a share of GDP across NATO countries.”
|
|
81
|
+
|
|
82
|
+
Resolve ids → bounded `jst query` → deeper `series observations` only if needed →
|
|
83
|
+
answer from structured CLI output. No TUI or workflow required.
|
|
84
|
+
|
|
85
|
+
### Human handoff
|
|
86
|
+
|
|
87
|
+
Example: “Set me up to explore defense spending across Europe.”
|
|
88
|
+
|
|
89
|
+
Resolve resources → write Session JSON → compose/save a workflow when useful → hand
|
|
90
|
+
the user the launch command. Do not substitute extra research for completing the
|
|
91
|
+
handoff.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
_DATA_MODEL = """\
|
|
95
|
+
## Data model
|
|
96
|
+
|
|
97
|
+
- **Metric** — a measurable theme (e.g. GDP, employed-persons). Identified by a slug id.
|
|
98
|
+
- **Entity** — a context the metric is measured in (e.g. a country, a company). Slug id.
|
|
99
|
+
- **Taxonomy** — a named population of entities (e.g. `country`, `us-county`,
|
|
100
|
+
`sec-central-index-key`). Use it to scope search, query, and ranking to that population.
|
|
101
|
+
- **Series** — one concrete time series: a metric observed for an entity (at a frequency).
|
|
102
|
+
- **Session** — portable analytical intent (resource ids + query filters). See **Sessions**
|
|
103
|
+
below. No observations live in a session file.
|
|
104
|
+
|
|
105
|
+
Ids are opaque slugs. Display names are not ids.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
_RESOLUTION = """\
|
|
109
|
+
## Resolution
|
|
110
|
+
|
|
111
|
+
Never invent metric, entity, taxonomy, or series ids from English names.
|
|
112
|
+
|
|
113
|
+
Always resolve via search (prefer a query string) before writing ids into a workflow,
|
|
114
|
+
`--session` JSON, or `jst query` flags. Scope with `--taxonomy` / `--relation` when the
|
|
115
|
+
user names a population or relationship. Confirm ambiguous hits with `show` before use.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
_RECIPES = """\
|
|
119
|
+
## Common recipes
|
|
120
|
+
|
|
121
|
+
### Find relevant metrics
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
jst metric search "defense spending" --taxonomy country --limit 20 --format json
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
If insufficient, try one or two synonyms the same way. Inspect only promising ids:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
jst metric show <resolved-id> --format json
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Stop once a useful candidate set exists. Do not page the full catalog.
|
|
134
|
+
|
|
135
|
+
### Compare latest values
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
jst query \\
|
|
139
|
+
--metric <metric-id> \\
|
|
140
|
+
--taxonomy country \\
|
|
141
|
+
--tail 1 \\
|
|
142
|
+
--sort-by value \\
|
|
143
|
+
--limit 50 \\
|
|
144
|
+
--format json
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Retrieve history after narrowing
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
jst metric series <metric-id> --format json
|
|
151
|
+
jst series observations <series-id> \\
|
|
152
|
+
--start-date 2015-01-01 \\
|
|
153
|
+
--format json
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Prepare a human investigation
|
|
157
|
+
|
|
158
|
+
1. Resolve metric/entity/taxonomy ids via search.
|
|
159
|
+
2. Write a Session JSON with those ids and filters (`taxonomy`, `tail`, …).
|
|
160
|
+
3. Create or reuse a workflow if the human needs a TUI pipeline.
|
|
161
|
+
4. Hand them a launch command, for example:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
jst workflow run eu-analysis --session defense.json
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Do not substitute extra research for completing the requested handoff.
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
_COMPOSITION = """\
|
|
171
|
+
## Composition and workflows
|
|
172
|
+
|
|
173
|
+
Interactive investigation is composed in the shell, not a custom DSL:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
jst run STEP [ARGS...] : STEP [ARGS...] : ...
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
- **Steps** are TUI screens for the **human**. They edit one shared session for the run.
|
|
180
|
+
- **Step args** seed that step; they are not the session.
|
|
181
|
+
- **Saved workflows** (`jst workflow create` / `run`) persist step topology + step args
|
|
182
|
+
only. Bake discovered metrics/entities into a **session JSON** and pass `--session`
|
|
183
|
+
when running (see **Sessions**).
|
|
184
|
+
- Pipeline tokens for `workflow create` must follow a `--` boundary, e.g.
|
|
185
|
+
`jst workflow create --id gdp-rank -- console : rank --taxonomy country`.
|
|
186
|
+
|
|
187
|
+
`jst run` and `jst workflow run` launch interactive TUIs. Agents should normally prepare
|
|
188
|
+
the Session/workflow and provide the launch command rather than controlling the TUI.
|
|
189
|
+
For step-specific help, run `jst step <id>` (or `jst step <id> --json`) on demand.
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
_SESSION_HOWTO = """\
|
|
193
|
+
### Create and modify
|
|
194
|
+
|
|
195
|
+
**Write a JSON file** (usual agent path after resolving ids):
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"metric": ["employed-persons", "unemployment-rate"],
|
|
200
|
+
"taxonomy": "country",
|
|
201
|
+
"tail": 1,
|
|
202
|
+
"sort_by": "value"
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Omit unused fields. Empty lists may be omitted. Ids must already be resolved.
|
|
207
|
+
|
|
208
|
+
**Python API** (same shape as the JSON file):
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from jstdata import Session
|
|
212
|
+
|
|
213
|
+
session = Session(
|
|
214
|
+
metric=["employed-persons", "unemployment-rate"],
|
|
215
|
+
taxonomy="country",
|
|
216
|
+
tail=1,
|
|
217
|
+
sort_by="value",
|
|
218
|
+
)
|
|
219
|
+
session.add_metric("gdp") # no-op if already present
|
|
220
|
+
session.add_entity("united-states")
|
|
221
|
+
session.remove_id("gdp") # removes from metric/entity/series
|
|
222
|
+
session.save("labor.json") # write JSON
|
|
223
|
+
session = Session.load("labor.json")
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Edit the JSON by hand or re-save from Python; there is no separate session CLI.
|
|
227
|
+
|
|
228
|
+
### Use with steps and workflows
|
|
229
|
+
|
|
230
|
+
Preload into a pipeline or saved workflow:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
jst run --session labor.json rank --taxonomy country
|
|
234
|
+
jst workflow run gdp-rank --session labor.json
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
`--session` copies the file into the live session at startup. The human can still
|
|
238
|
+
change it in the TUI. Typical agent pattern after metric discovery:
|
|
239
|
+
|
|
240
|
+
1. Resolve metric (and taxonomy) ids via search.
|
|
241
|
+
2. Write a session JSON with those `metric` ids (and optional filters).
|
|
242
|
+
3. Create or reuse a workflow whose steps consume session metrics (e.g. `rank`).
|
|
243
|
+
4. Hand the user: `jst workflow run <id> --session <file>.json`.
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def package_version() -> str:
|
|
248
|
+
try:
|
|
249
|
+
return version(_PACKAGE)
|
|
250
|
+
except PackageNotFoundError:
|
|
251
|
+
return "unknown"
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _first_line(text: str | None) -> str:
|
|
255
|
+
if not text:
|
|
256
|
+
return ""
|
|
257
|
+
line = text.strip().splitlines()[0].strip()
|
|
258
|
+
# Click often embeds ``\\b`` blocks; collapse leftover whitespace.
|
|
259
|
+
return re.sub(r"\s+", " ", line)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _md_cell(text: str) -> str:
|
|
263
|
+
return text.replace("|", "\\|").replace("\n", " ").strip()
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _param_token(param: click.Parameter) -> str | None:
|
|
267
|
+
"""Compact signature fragment for one Click parameter."""
|
|
268
|
+
if getattr(param, "hidden", False):
|
|
269
|
+
return None
|
|
270
|
+
if isinstance(param, click.Argument):
|
|
271
|
+
name = (param.metavar or param.name or "ARG").upper()
|
|
272
|
+
if param.nargs == -1:
|
|
273
|
+
name = f"{name}..."
|
|
274
|
+
return name if param.required else f"[{name}]"
|
|
275
|
+
if isinstance(param, click.Option):
|
|
276
|
+
# Prefer the long form.
|
|
277
|
+
opt = next((o for o in param.opts if o.startswith("--")), None)
|
|
278
|
+
if opt is None:
|
|
279
|
+
opt = param.opts[0] if param.opts else f"--{param.name}"
|
|
280
|
+
if param.is_flag or param.count:
|
|
281
|
+
return opt if param.required else f"[{opt}]"
|
|
282
|
+
meta = (param.metavar or param.name or "VALUE").upper()
|
|
283
|
+
token = f"{opt} {meta}"
|
|
284
|
+
return token if param.required else f"[{token}]"
|
|
285
|
+
return None
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def format_command_params(cmd: click.Command) -> str:
|
|
289
|
+
parts: list[str] = []
|
|
290
|
+
for param in cmd.params:
|
|
291
|
+
token = _param_token(param)
|
|
292
|
+
if token:
|
|
293
|
+
parts.append(token)
|
|
294
|
+
return " ".join(parts)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def iter_leaf_commands(
|
|
298
|
+
root: click.Group,
|
|
299
|
+
*,
|
|
300
|
+
prog: str = "jst",
|
|
301
|
+
) -> Iterable[tuple[str, click.Command]]:
|
|
302
|
+
"""Yield ``(invocation, command)`` for every leaf command under ``root``."""
|
|
303
|
+
|
|
304
|
+
def walk(cmd: click.Command, parts: list[str], ctx: click.Context) -> Iterable[tuple[str, click.Command]]:
|
|
305
|
+
if isinstance(cmd, click.Group):
|
|
306
|
+
seen: set[int] = set()
|
|
307
|
+
for name in sorted(cmd.list_commands(ctx)):
|
|
308
|
+
sub = cmd.get_command(ctx, name)
|
|
309
|
+
if sub is None:
|
|
310
|
+
continue
|
|
311
|
+
# Skip Click aliases that point at the same command object.
|
|
312
|
+
sub_id = id(sub)
|
|
313
|
+
if sub_id in seen:
|
|
314
|
+
continue
|
|
315
|
+
seen.add(sub_id)
|
|
316
|
+
sub_ctx = click.Context(sub, info_name=name, parent=ctx)
|
|
317
|
+
yield from walk(sub, parts + [name], sub_ctx)
|
|
318
|
+
return
|
|
319
|
+
invocation = " ".join(parts)
|
|
320
|
+
yield invocation, cmd
|
|
321
|
+
|
|
322
|
+
root_ctx = click.Context(root, info_name=prog)
|
|
323
|
+
yield from walk(root, [prog], root_ctx)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def render_commands_table(root: click.Group) -> str:
|
|
327
|
+
lines = [
|
|
328
|
+
"| Command | Description | Parameters |",
|
|
329
|
+
"|---------|-------------|------------|",
|
|
330
|
+
]
|
|
331
|
+
for invocation, cmd in iter_leaf_commands(root):
|
|
332
|
+
desc = _first_line(cmd.help) or _first_line(cmd.short_help) or ""
|
|
333
|
+
params = format_command_params(cmd)
|
|
334
|
+
lines.append(
|
|
335
|
+
f"| `{_md_cell(invocation)}` | {_md_cell(desc)} | {_md_cell(params)} |"
|
|
336
|
+
)
|
|
337
|
+
return "\n".join(lines)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _format_step_args(spec) -> str:
|
|
341
|
+
if not spec.arguments:
|
|
342
|
+
return "_(none)_"
|
|
343
|
+
bits: list[str] = []
|
|
344
|
+
for arg in spec.arguments:
|
|
345
|
+
flag = arg.flag()
|
|
346
|
+
piece = f"`{flag}`"
|
|
347
|
+
if arg.multiple:
|
|
348
|
+
piece += " (repeatable)"
|
|
349
|
+
if arg.required:
|
|
350
|
+
piece += " (required)"
|
|
351
|
+
elif arg.default is not None:
|
|
352
|
+
piece += f" [default: `{arg.default}`]"
|
|
353
|
+
if arg.choices:
|
|
354
|
+
choices = ", ".join(f"`{c}`" for c in arg.choices)
|
|
355
|
+
piece += f" {{{choices}}}"
|
|
356
|
+
bits.append(piece)
|
|
357
|
+
return ", ".join(bits)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _annotation_label(annotation: object) -> str:
|
|
361
|
+
"""Human-readable type for a Session field annotation."""
|
|
362
|
+
origin = get_origin(annotation)
|
|
363
|
+
if origin is list:
|
|
364
|
+
args = get_args(annotation)
|
|
365
|
+
inner = _annotation_label(args[0]) if args else "any"
|
|
366
|
+
return f"list[{inner}]"
|
|
367
|
+
if origin is not None:
|
|
368
|
+
# Optional[T] / Union[T, None]
|
|
369
|
+
args = [a for a in get_args(annotation) if a is not type(None)]
|
|
370
|
+
if len(args) == 1:
|
|
371
|
+
return f"{_annotation_label(args[0])} | null"
|
|
372
|
+
if annotation is str:
|
|
373
|
+
return "string"
|
|
374
|
+
if annotation is int:
|
|
375
|
+
return "integer"
|
|
376
|
+
if isinstance(annotation, type):
|
|
377
|
+
return annotation.__name__
|
|
378
|
+
text = str(annotation)
|
|
379
|
+
return text.replace("typing.", "").replace("None", "null")
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
_SESSION_FIELD_NOTES: dict[str, str] = {
|
|
383
|
+
"metric": "Resolved metric slug ids staged for the investigation",
|
|
384
|
+
"entity": "Resolved entity slug ids",
|
|
385
|
+
"series": "Resolved series slug ids (when targeting series directly)",
|
|
386
|
+
"frequency": "Optional frequency filter (Annual, Quarterly, Monthly, Daily, Intraday)",
|
|
387
|
+
"taxonomy": "Restrict analysis to entities in this population (taxonomy slug)",
|
|
388
|
+
"head": "Earliest N observations per series for `/query`",
|
|
389
|
+
"tail": "Latest N observations per series for `/query`",
|
|
390
|
+
"as_of": "Timezone-aware ISO-8601 cutoff on release_timestamp",
|
|
391
|
+
"sort_by": "`id` (default) or `value` (desc) for `/query` ordering",
|
|
392
|
+
"order_by": "Observation order preference where applicable (`asc`/`desc`)",
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def render_session_section() -> str:
|
|
397
|
+
"""Sessions how-to plus a field table derived from ``Session``."""
|
|
398
|
+
lines = [
|
|
399
|
+
"## Sessions",
|
|
400
|
+
"",
|
|
401
|
+
"A **session** is the portable bag of resource ids and filters that steps share.",
|
|
402
|
+
"It mirrors `jst query` / `JSTDataClient.query` intent: no observations, only what",
|
|
403
|
+
"is needed to (re)run a query or drive a TUI pipeline.",
|
|
404
|
+
"",
|
|
405
|
+
"Saved workflows do **not** store session contents. After discovering metrics or",
|
|
406
|
+
"entities, write them into a session JSON and pass `--session` on `jst run` or",
|
|
407
|
+
"`jst workflow run`. Steps such as `rank` (metrics in session) and `discover`",
|
|
408
|
+
"(entities in session) read that staged state.",
|
|
409
|
+
"",
|
|
410
|
+
"### Session JSON fields",
|
|
411
|
+
"",
|
|
412
|
+
"Primary fields for agent-written sessions (from the installed `Session` model).",
|
|
413
|
+
"Prefer `head` / `tail` / `as_of` for query windows. Omit unused fields.",
|
|
414
|
+
"",
|
|
415
|
+
"| Field | Type | Notes |",
|
|
416
|
+
"|-------|------|-------|",
|
|
417
|
+
]
|
|
418
|
+
for f in fields(Session):
|
|
419
|
+
if f.name in _SESSION_FIELDS_OMIT:
|
|
420
|
+
continue
|
|
421
|
+
note = _SESSION_FIELD_NOTES.get(f.name, "")
|
|
422
|
+
lines.append(
|
|
423
|
+
f"| `{_md_cell(f.name)}` | {_md_cell(_annotation_label(f.type))} | {_md_cell(note)} |"
|
|
424
|
+
)
|
|
425
|
+
lines.append("")
|
|
426
|
+
lines.append(_SESSION_HOWTO.rstrip())
|
|
427
|
+
return "\n".join(lines)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def render_steps_section() -> str:
|
|
431
|
+
specs = list_steps()
|
|
432
|
+
lines = [
|
|
433
|
+
"## Interactive steps",
|
|
434
|
+
"",
|
|
435
|
+
"Atoms for `jst run` / saved workflows (human-facing TUIs). Order-agnostic: any",
|
|
436
|
+
"step accepts an empty or arbitrary session. Do not operate these UIs yourself;",
|
|
437
|
+
"use `jst step <id>` if you need details while helping a human.",
|
|
438
|
+
"",
|
|
439
|
+
"| Id | Name | Description | Arguments | Example |",
|
|
440
|
+
"|----|------|-------------|-----------|---------|",
|
|
441
|
+
]
|
|
442
|
+
if not specs:
|
|
443
|
+
lines.append("| _(none registered)_ | | | | |")
|
|
444
|
+
return "\n".join(lines)
|
|
445
|
+
|
|
446
|
+
for spec in specs:
|
|
447
|
+
lines.append(
|
|
448
|
+
"| "
|
|
449
|
+
+ " | ".join(
|
|
450
|
+
[
|
|
451
|
+
f"`{_md_cell(spec.id)}`",
|
|
452
|
+
_md_cell(spec.name),
|
|
453
|
+
_md_cell(spec.description),
|
|
454
|
+
_md_cell(_format_step_args(spec)),
|
|
455
|
+
f"`{_md_cell(spec.example or f'jst run {spec.id}')}`",
|
|
456
|
+
]
|
|
457
|
+
)
|
|
458
|
+
+ " |"
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
lines.append("")
|
|
462
|
+
lines.append("### Step details")
|
|
463
|
+
for spec in specs:
|
|
464
|
+
lines.append("")
|
|
465
|
+
lines.append(f"#### `{spec.id}` — {spec.name}")
|
|
466
|
+
lines.append("")
|
|
467
|
+
lines.append(spec.description.strip())
|
|
468
|
+
lines.append("")
|
|
469
|
+
if spec.arguments:
|
|
470
|
+
lines.append("Arguments:")
|
|
471
|
+
lines.append("")
|
|
472
|
+
for arg in spec.arguments:
|
|
473
|
+
req = "required" if arg.required else "optional"
|
|
474
|
+
default = (
|
|
475
|
+
f", default `{arg.default}`" if arg.default is not None else ""
|
|
476
|
+
)
|
|
477
|
+
multi = ", repeatable" if arg.multiple else ""
|
|
478
|
+
lines.append(
|
|
479
|
+
f"- `{arg.flag()}` ({arg.type}, {req}{multi}{default}): "
|
|
480
|
+
f"{arg.description}"
|
|
481
|
+
)
|
|
482
|
+
if arg.choices:
|
|
483
|
+
lines.append(
|
|
484
|
+
f" - Choices: {', '.join(f'`{c}`' for c in arg.choices)}"
|
|
485
|
+
)
|
|
486
|
+
lines.append("")
|
|
487
|
+
example = spec.example or f"jst run {spec.id}"
|
|
488
|
+
lines.append(f"Example: `{example}`")
|
|
489
|
+
return "\n".join(lines)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def render_agent_guide(cli_group: click.Group) -> str:
|
|
493
|
+
"""Build the full markdown guide for the given Click root group."""
|
|
494
|
+
ver = package_version()
|
|
495
|
+
sections = [
|
|
496
|
+
f"# jstdata agent guide (v{ver})",
|
|
497
|
+
"",
|
|
498
|
+
"Operating manual for agents helping a user with this installed CLI. "
|
|
499
|
+
"Prefer this document over guessing command shapes or resource ids. "
|
|
500
|
+
"Follow **Hard rules** and **Choose an operating mode** before browsing the "
|
|
501
|
+
"full command tables.",
|
|
502
|
+
"",
|
|
503
|
+
_HARD_RULES.rstrip(),
|
|
504
|
+
"",
|
|
505
|
+
_OPERATING_POLICY.rstrip(),
|
|
506
|
+
"",
|
|
507
|
+
_OPERATING_MODES.rstrip(),
|
|
508
|
+
"",
|
|
509
|
+
_DATA_MODEL.rstrip(),
|
|
510
|
+
"",
|
|
511
|
+
_RESOLUTION.rstrip(),
|
|
512
|
+
"",
|
|
513
|
+
_RECIPES.rstrip(),
|
|
514
|
+
"",
|
|
515
|
+
_COMPOSITION.rstrip(),
|
|
516
|
+
"",
|
|
517
|
+
render_session_section(),
|
|
518
|
+
"",
|
|
519
|
+
render_steps_section(),
|
|
520
|
+
"",
|
|
521
|
+
"## CLI commands",
|
|
522
|
+
"",
|
|
523
|
+
"One row per leaf command, derived from the installed CLI. Prefer the recipes "
|
|
524
|
+
"and hard rules above; use this table to confirm flags, not as a checklist to "
|
|
525
|
+
"execute.",
|
|
526
|
+
"",
|
|
527
|
+
render_commands_table(cli_group),
|
|
528
|
+
"",
|
|
529
|
+
]
|
|
530
|
+
return "\n".join(sections)
|