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.
@@ -0,0 +1,163 @@
1
+ """Interactive tutorial coach for the bundled ``tutorial`` workflow."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from importlib.resources import files
7
+ from typing import Any, Callable, Mapping, Optional
8
+
9
+ import yaml
10
+
11
+ from ..client import JSTDataClient
12
+ from ..session import Session
13
+ from .base import default_session_path
14
+ from .export import ExportModal
15
+ from .store import (
16
+ TUTORIAL_WORKFLOW_ID,
17
+ load_workflow,
18
+ resolve_saved_workflow,
19
+ )
20
+
21
+ _SCRIPT_PATH = "tutorial_script.yaml"
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class TutorialPhase:
26
+ text: str
27
+ step_index: Optional[int] = None
28
+ until: Mapping[str, Any] = field(default_factory=dict)
29
+
30
+ @classmethod
31
+ def from_dict(cls, data: dict[str, Any]) -> TutorialPhase:
32
+ until = data.get("until") or {}
33
+ if not isinstance(until, dict):
34
+ raise ValueError("Tutorial phase 'until' must be a mapping.")
35
+ step_index = data.get("step_index")
36
+ if step_index is not None:
37
+ step_index = int(step_index)
38
+ return cls(
39
+ text=str(data.get("text") or "").strip(),
40
+ step_index=step_index,
41
+ until=until,
42
+ )
43
+
44
+
45
+ @dataclass
46
+ class TutorialCoach:
47
+ phases: tuple[TutorialPhase, ...]
48
+ phase_index: int = 0
49
+
50
+ @classmethod
51
+ def load(cls) -> TutorialCoach:
52
+ raw = files("jstdata.workflows.bundled").joinpath(_SCRIPT_PATH).read_text(
53
+ encoding="utf-8"
54
+ )
55
+ data = yaml.safe_load(raw) or {}
56
+ items = data.get("phases") or []
57
+ if not items:
58
+ raise ValueError("Tutorial script has no phases.")
59
+ return cls(phases=tuple(TutorialPhase.from_dict(p) for p in items))
60
+
61
+ @property
62
+ def is_complete(self) -> bool:
63
+ return self.phase_index >= len(self.phases)
64
+
65
+ @property
66
+ def current_phase(self) -> Optional[TutorialPhase]:
67
+ if self.is_complete:
68
+ return None
69
+ return self.phases[self.phase_index]
70
+
71
+ @property
72
+ def progress_label(self) -> str:
73
+ total = len(self.phases)
74
+ current = min(self.phase_index + 1, total)
75
+ return f"Step {current} / {total}"
76
+
77
+ def _phase_matches(
78
+ self,
79
+ phase: TutorialPhase,
80
+ session: Session,
81
+ step_index: int,
82
+ action: Optional[str],
83
+ ) -> bool:
84
+ until = phase.until or {}
85
+ if until.get("done"):
86
+ return False
87
+ if until.get("auto"):
88
+ return True
89
+ if phase.step_index is not None and phase.step_index != step_index:
90
+ return False
91
+ if "session_entity_count" in until:
92
+ needed = int(until["session_entity_count"])
93
+ return len(session.entity) >= needed
94
+ if "session_metric_count" in until:
95
+ needed = int(until["session_metric_count"])
96
+ return len(session.metric) >= needed
97
+ if "action" in until:
98
+ return action == until["action"]
99
+ return False
100
+
101
+ def maybe_advance(
102
+ self,
103
+ session: Session,
104
+ step_index: int,
105
+ action: Optional[str] = None,
106
+ ) -> bool:
107
+ """Advance when the current phase's completion rule is satisfied."""
108
+ if self.is_complete:
109
+ return False
110
+ phase = self.current_phase
111
+ assert phase is not None
112
+ if not self._phase_matches(phase, session, step_index, action):
113
+ return False
114
+ self.phase_index += 1
115
+ return True
116
+
117
+ def gate_progress(self, session: Session, step_index: int) -> bool:
118
+ """True when navigation should be blocked (incomplete count-based phase)."""
119
+ phase = self.current_phase
120
+ if phase is None or self.is_complete:
121
+ return False
122
+ if phase.until.get("action") == "next_step":
123
+ return False
124
+ if phase.step_index is not None and phase.step_index != step_index:
125
+ return False
126
+ until = phase.until or {}
127
+ if "session_entity_count" in until:
128
+ return len(session.entity) < int(until["session_entity_count"])
129
+ if "session_metric_count" in until:
130
+ return len(session.metric) < int(until["session_metric_count"])
131
+ return False
132
+
133
+
134
+ class TutorialCoachBar:
135
+ """Mixin-friendly helper; actual widget lives in tutorial_host.py."""
136
+
137
+ @staticmethod
138
+ def format_header(progress: str) -> str:
139
+ return f"TUTORIAL · {progress}"
140
+
141
+
142
+ def run_tutorial(
143
+ client: JSTDataClient,
144
+ *,
145
+ session_path: str | None = None,
146
+ output_path: str | None = None,
147
+ ) -> None:
148
+ """Load bundled tutorial workflow and launch the coached host."""
149
+ from .tutorial_host import TutorialHost
150
+
151
+ workflow = load_workflow(TUTORIAL_WORKFLOW_ID)
152
+ resolved = resolve_saved_workflow(workflow)
153
+ coach = TutorialCoach.load()
154
+ prefix = "-".join(s.spec.id for s in resolved)
155
+ out = output_path or default_session_path(prefix)
156
+ app = TutorialHost(
157
+ client,
158
+ resolved,
159
+ coach,
160
+ session_path=session_path,
161
+ output_path=out,
162
+ )
163
+ app.run()
@@ -0,0 +1,178 @@
1
+ """Tutorial host: WorkflowHost plus a persistent coach bar."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from textual.app import ComposeResult
6
+ from textual.screen import Screen
7
+ from textual.widgets import Label, Static
8
+
9
+ from .export import ExportModal
10
+ from .host import WorkflowHost
11
+ from .tutorial import TutorialCoach, TutorialCoachBar
12
+
13
+
14
+ class CoachBarWidget(Static):
15
+ """Docked instruction strip shown for the whole tutorial run."""
16
+
17
+ DEFAULT_CSS = """
18
+ CoachBarWidget {
19
+ dock: top;
20
+ height: auto;
21
+ min-height: 4;
22
+ max-height: 8;
23
+ background: #1a1a2e;
24
+ border-bottom: thick #38bdf8;
25
+ padding: 0 1;
26
+ }
27
+
28
+ #coach-header {
29
+ color: #38bdf8;
30
+ text-style: bold;
31
+ height: 1;
32
+ }
33
+
34
+ #coach-body {
35
+ color: #e0e0e0;
36
+ height: auto;
37
+ padding-bottom: 1;
38
+ }
39
+ """
40
+
41
+ def __init__(self, header: str = "", body: str = "") -> None:
42
+ super().__init__()
43
+ self._header = header
44
+ self._body = body
45
+
46
+ def compose(self) -> ComposeResult:
47
+ yield Label(self._header, id="coach-header")
48
+ yield Static(self._body, id="coach-body")
49
+
50
+ def update_coach(self, header: str, body: str) -> None:
51
+ if not self.is_mounted:
52
+ self._header = header
53
+ self._body = body
54
+ return
55
+ self.query_one("#coach-header", Label).update(header)
56
+ self.query_one("#coach-body", Static).update(body)
57
+
58
+
59
+ class TutorialHost(WorkflowHost):
60
+ """WorkflowHost with phased on-screen guidance for the bundled tutorial."""
61
+
62
+ def __init__(
63
+ self,
64
+ client,
65
+ steps,
66
+ coach: TutorialCoach,
67
+ session_path: str | None = None,
68
+ output_path: str | None = None,
69
+ ) -> None:
70
+ super().__init__(client, steps, session_path=session_path, output_path=output_path)
71
+ self.coach = coach
72
+ self._coach_bar: CoachBarWidget | None = None
73
+
74
+ def on_mount(self) -> None:
75
+ self.coach.maybe_advance(self.session, self.step_index, None)
76
+ self.set_interval(0.25, self._poll_coach)
77
+ self._push_current_step()
78
+
79
+ def _coach_text(self) -> tuple[str, str]:
80
+ phase = self.coach.current_phase
81
+ if phase is None:
82
+ return (
83
+ "TUTORIAL · complete",
84
+ "Tutorial complete. Press q to quit.",
85
+ )
86
+ return (
87
+ TutorialCoachBar.format_header(self.coach.progress_label),
88
+ phase.text,
89
+ )
90
+
91
+ def _poll_coach(self) -> None:
92
+ if self.coach.maybe_advance(self.session, self.step_index, None):
93
+ self._refresh_coach()
94
+
95
+ def _refresh_coach(self) -> None:
96
+ if self._coach_bar is None or not self._coach_bar.is_mounted:
97
+ return
98
+ header, body = self._coach_text()
99
+ self._coach_bar.update_coach(header, body)
100
+
101
+ def _push_current_step(self) -> None:
102
+ self.push_screen(self._make_current_screen())
103
+ self.call_after_refresh(self._mount_coach_on_active_screen)
104
+
105
+ def _mount_coach_on_active_screen(self) -> None:
106
+ """Mount coach on the active step screen (App-level mounts sit behind Screens)."""
107
+ screen = self.screen
108
+ if not isinstance(screen, Screen):
109
+ return
110
+ if self._coach_bar is not None and self._coach_bar.is_mounted:
111
+ self._coach_bar.remove()
112
+ header, body = self._coach_text()
113
+ self._coach_bar = CoachBarWidget(header, body)
114
+ screen.mount(self._coach_bar)
115
+
116
+ def _after_step_change(self) -> None:
117
+ self.call_after_refresh(self._mount_coach_on_active_screen)
118
+
119
+ def check_action(self, action: str, parameters: tuple[object, ...]) -> bool | None:
120
+ phase = self.coach.current_phase
121
+ if (
122
+ action == "next_step"
123
+ and phase is not None
124
+ and phase.until.get("action") == "next_step"
125
+ ):
126
+ return True
127
+ return super().check_action(action, parameters)
128
+
129
+ def action_next_step(self) -> None:
130
+ phase = self.coach.current_phase
131
+ if phase is not None and not self.coach.is_complete:
132
+ if phase.until.get("action") != "next_step":
133
+ if self.coach.gate_progress(self.session, self.step_index):
134
+ self.notify(
135
+ "Finish the current tutorial step first.",
136
+ severity="warning",
137
+ )
138
+ return
139
+ if self.step_index >= len(self.steps) - 1:
140
+ self.notify("Last step in the pipeline", severity="warning")
141
+ return
142
+ advanced = self.coach.maybe_advance(
143
+ self.session, self.step_index, "next_step"
144
+ )
145
+ self._pop_overlays()
146
+ self.step_index += 1
147
+ self.switch_screen(self._make_current_screen())
148
+ self._after_step_change()
149
+ if advanced:
150
+ self._refresh_coach()
151
+
152
+ def action_prev_step(self) -> None:
153
+ if self.step_index <= 0:
154
+ self.notify("First step in the pipeline", severity="warning")
155
+ return
156
+ self._pop_overlays()
157
+ self.step_index -= 1
158
+ self.switch_screen(self._make_current_screen())
159
+ self._after_step_change()
160
+
161
+ def action_session(self) -> None:
162
+ super().action_session()
163
+ if self.coach.maybe_advance(self.session, self.step_index, "open_session"):
164
+ self._refresh_coach()
165
+
166
+ def action_export(self) -> None:
167
+ self.push_screen(
168
+ ExportModal(
169
+ self.session,
170
+ default_path=self.output_path,
171
+ on_write=self._on_tutorial_session_written,
172
+ )
173
+ )
174
+
175
+ def _on_tutorial_session_written(self, path: str) -> None:
176
+ if self.coach.maybe_advance(self.session, self.step_index, "session_written"):
177
+ self._refresh_coach()
178
+ self.notify(f"Tutorial complete — session saved to {path}")
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: jstdata
3
+ Version: 0.2.0
4
+ Summary: Python API, CLI, and interactive workflows for Jefferson Street financial and economic data.
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Keywords: api,cli,data,jefferson street,economics,finance
8
+ Author: Mahfuj Ahbab
9
+ Requires-Python: >=3.10,<4.0
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Dist: click (>=8.2.1,<9.0.0)
21
+ Requires-Dist: pandas (>=2.3.1,<3.0.0)
22
+ Requires-Dist: pyperclip (>=1.9.0,<2.0.0)
23
+ Requires-Dist: pyyaml (>=6.0.3,<7.0.0)
24
+ Requires-Dist: requests (>=2.31.0,<3.0.0)
25
+ Requires-Dist: tabulate (>=0.9.0,<0.10.0)
26
+ Requires-Dist: textual (>=1.0.0,<2.0.0)
27
+ Project-URL: Homepage, https://github.com/Jefferson-Street-Technologies/jstdata
28
+ Project-URL: Repository, https://github.com/Jefferson-Street-Technologies/jstdata
29
+ Description-Content-Type: text/markdown
30
+
31
+ # Jefferson Street Data (jstdata)
32
+
33
+ A Python interface and Research OS for the Jefferson Street financial and economic data API.
34
+
35
+ ## Design Philosophy: From Filing Cabinet to Knowledge Graph
36
+
37
+ `jstdata` is built to minimize the friction between human thought and actionable data.
38
+
39
+ - **Series-First**: The individual data series is the primary resource, carrying its own metadata (frequency, units, source).
40
+ - **Entity Graph**: Entities are nodes in a relational graph — walk from a company to its SIC, a security ticker, or a geography.
41
+ - **Taxonomies**: Named populations (`country`, `sec-central-index-key`, …) scope search, query, and ranking.
42
+ - **Intent over IDs**: CLI and Python favor fuzzy search over memorizing slugs; always resolve before querying.
43
+ - **Sessions & workflows**: Stage resolved ids in a session JSON; compose interactive steps in the shell and save them as named workflows.
44
+ - **Reproducible Discovery**: High-speed discovery in the TUI transitions into immutable Python / CLI snippets.
45
+
46
+ ---
47
+
48
+ ## Installation
49
+
50
+ ```bash
51
+ # Using poetry
52
+ poetry install
53
+
54
+ # Manual install
55
+ pip install .
56
+ ```
57
+
58
+ ## Authentication & Configuration
59
+
60
+ ### Quick Start (CLI)
61
+
62
+ ```bash
63
+ jst login
64
+ ```
65
+
66
+ Credentials are saved to `~/.jstdata/config.json` with secure permissions.
67
+
68
+ ### Environment Variables
69
+
70
+ These take precedence over the config file:
71
+
72
+ ```bash
73
+ export JSTDATA_API_KEY="your-api-key-here"
74
+ # Optional: export JSTDATA_BASE_URL="https://api.jeffersonst.io"
75
+ ```
76
+
77
+ ### Managing Configuration
78
+
79
+ ```bash
80
+ jst config show
81
+ jst config show --verbose
82
+ jst config set api_key XXX
83
+ ```
84
+
85
+ ## Interactive investigation (`jst run`)
86
+
87
+ Compose TUI **steps** in the shell. Steps share one **Session** (staged metrics /
88
+ entities / series + query filters) for the lifetime of the run.
89
+
90
+ ```bash
91
+ # Catalog search → stage entities/metrics/series
92
+ jst run console
93
+
94
+ # Scope to a taxonomy and resource type
95
+ jst run console --taxonomy sec-central-index-key --resource-type entity
96
+
97
+ # Filter entities by typed graph edges (repeatable; OR'd)
98
+ jst run console --relation classified_as:sic:7372 --resource-type entity
99
+
100
+ # Chain steps
101
+ jst run console --taxonomy country : discover --mode union : rank --taxonomy country
102
+
103
+ # Preload a session JSON; set default export path
104
+ jst run --session labor.json --output out.json rank --taxonomy country
105
+ ```
106
+
107
+ Built-in steps:
108
+
109
+ | Step | Role |
110
+ |------|------|
111
+ | `console` | Search catalog; stage into the session |
112
+ | `discover` | Find metrics for session entities; preview coverage |
113
+ | `rank` | Leaderboard entities for a session metric |
114
+
115
+ Host keys (every step): `s` session · `f` find · `e` export · `n`/`p` next/prev · `?` step help · `q` quit.
116
+
117
+ ```bash
118
+ jst steps
119
+ jst step console
120
+ jst step rank --json
121
+ ```
122
+
123
+ ### Saved workflows
124
+
125
+ Persist step pipelines (not session contents) under `~/.jstdata/workflows/`:
126
+
127
+ ```bash
128
+ jst workflow create --id gdp-rank --description "GDP board" -- \
129
+ console --taxonomy country : rank --taxonomy country
130
+
131
+ jst workflow ls
132
+ jst workflow run gdp-rank --session labor.json
133
+ jst workflow rm gdp-rank
134
+ ```
135
+
136
+ (`jst workflows` remains a compatibility alias.)
137
+
138
+ ### Tutorial
139
+
140
+ ```bash
141
+ jst tutorial
142
+ # same as: jst workflow run tutorial
143
+ ```
144
+
145
+ ### Agents
146
+
147
+ ```bash
148
+ jst agent-guide
149
+ ```
150
+
151
+ Prints a version-tied operating manual (hard rules, modes, recipes, and the live
152
+ CLI/step reference). Prefer it over guessing command shapes or inventing ids.
153
+
154
+ ## The Scriptable CLI (`jst`)
155
+
156
+ Pipe-friendly commands for automation and quick extraction.
157
+
158
+ ```bash
159
+ # Fuzzy / set search (omit QUERY to list the matching set)
160
+ jst metric search "defense spending" --taxonomy country --limit 20 --format json
161
+ jst entity search --relation classified_as:sic:3674 --limit 50 --format json
162
+ jst entity search --metric gdp --metric cpi --mode intersect --format json
163
+
164
+ # Taxonomies
165
+ jst taxonomy ls --format json
166
+ jst taxonomy entities country --limit 20 --format json
167
+
168
+ # Bounded cross-sectional query (head/tail per series)
169
+ jst query --metric inflation --entity "United States" --frequency Monthly --tail 20
170
+ jst query --metric gdp --taxonomy country --tail 1 --sort-by value --limit 50 --format json
171
+
172
+ # Deep history for one known series
173
+ jst series observations ABC123 --start-date 2000-01-01 --limit 1000
174
+
175
+ # Entity graph
176
+ jst entity relations cik:1045810 --format pretty
177
+ ```
178
+
179
+ ## The Python API
180
+
181
+ ```python
182
+ from jstdata import JSTDataClient, Session
183
+
184
+ client = JSTDataClient()
185
+
186
+ # Cross-sectional observations (bounded per series)
187
+ df = client.query_df(
188
+ metric="gross-domestic-product",
189
+ entity=["usa", "gbr"],
190
+ tail=20,
191
+ )
192
+
193
+ # Rank a taxonomy population by latest value
194
+ top = client.query(
195
+ metric="gross-domestic-product",
196
+ taxonomy="country",
197
+ frequency="Annual",
198
+ tail=1,
199
+ sort_by="value",
200
+ limit=50,
201
+ )
202
+
203
+ # Relation-scoped entity search (OR when multiple)
204
+ entities = client.search_entities(
205
+ relation=["classified_as:sic:7372", "classified_as:sic:5961"],
206
+ taxonomy="sec-central-index-key",
207
+ limit=50,
208
+ )
209
+
210
+ # Deep history for one series
211
+ obs = client.get_series_observations("ABC123", start_date="2000-01-01")
212
+
213
+ # Stage intent for a later `jst run` / `jst workflow run`
214
+ session = Session(
215
+ metric=["gross-domestic-product"],
216
+ taxonomy="country",
217
+ tail=1,
218
+ sort_by="value",
219
+ )
220
+ session.save("labor.json")
221
+ ```
222
+
223
+ ## Extending the product
224
+
225
+ See [AGENTS.md](AGENTS.md) for how steps, the host, and saved workflows are
226
+ structured when adding new interactive surface area.
227
+
@@ -0,0 +1,28 @@
1
+ jstdata/__init__.py,sha256=HQVrTOLDbVmhAZVlxPNuysSwp7h02gipLPaPmzCmFPg,257
2
+ jstdata/agent_guide.py,sha256=i4MXf0ra16Do98IWeq2YJ0Tdyd__klCBQGDunPh6sQ4,18539
3
+ jstdata/cli.py,sha256=PeZwk8GZEPHQ-fveA31LSZEKcZS6Bs0DqGGZ9I-sXOU,20349
4
+ jstdata/client.py,sha256=U2HlIsSddLAeRCM2KDhZ9scA73sPP1hB6wfTXG_82Fs,18906
5
+ jstdata/models.py,sha256=bbipJxITf6K_5N2rVdv01QaxJp_8Y--ud92drhKJkLg,4656
6
+ jstdata/session.py,sha256=myRmghmSyZ98Co0tJ2MmO8AMo5HNyoe7OpCK9MLCMZI,8874
7
+ jstdata/utils.py,sha256=ge9nPasdCaJyEgEdH9z9-uZ1wmR-Fur5z798qcLOQLs,3198
8
+ jstdata/workflows/__init__.py,sha256=QVFkujbTsDqqcubbm7s9-fwoxeJllVxOuH_PZ_aZWEE,2033
9
+ jstdata/workflows/base.py,sha256=UvLRA6wDQdYyYZNqqFzhNRo-YK4D2vEiCJUA89Ks6NA,10650
10
+ jstdata/workflows/bundled/__init__.py,sha256=f4FveHIFSbteKjgLqhgnEwpheuigMolIudZqzSdX8YI,58
11
+ jstdata/workflows/bundled/tutorial.yaml,sha256=ECaWCXPvnD2PcHefJHRKsgMuyMyCJAp1bVs8yTClt2c,356
12
+ jstdata/workflows/bundled/tutorial_script.yaml,sha256=DVRtJnmRIAQrgIa2ZqPcwPTyx3TIOJgS4IO4yQsnxAw,952
13
+ jstdata/workflows/chart.py,sha256=jbXt-vuDfrH17zMUGZdYPBnyUk6qMoufufwISaR4fJE,11218
14
+ jstdata/workflows/console.py,sha256=8GkLr9Gp1veTF8LBEkgW7v9PjNxxMzt2_2HI54sWhnQ,18845
15
+ jstdata/workflows/discover.py,sha256=eUWIDf_4vlEsag8B9cEB2HCv5_KmOVlSN5RbEKScSVA,17359
16
+ jstdata/workflows/export.py,sha256=SvovNEf8vRHEUa-reI4YJg5S87gcelukd28b0oAlQrw,4879
17
+ jstdata/workflows/find.py,sha256=aKFlHjItbpB3gAPOdOxBV3GbL--ZSUBq-DjuPFf3Qzg,12104
18
+ jstdata/workflows/host.py,sha256=WfrXv2TIK96lZtkoqDkXjgHe__wK-mtQwkeYu5DWU0w,7772
19
+ jstdata/workflows/rank.py,sha256=vjK4dX1wOriwKUEuO9OWgucXRvMqo9iUzEFzaVPFlFM,22342
20
+ jstdata/workflows/session_manager.py,sha256=HBJfUkKSiKWTJhWxeOi12nPf8ToDnpsHwaHqApWEjbA,8527
21
+ jstdata/workflows/store.py,sha256=iFhaXrC0s5rIFNQDoMrHAYSqhBKS9X6tQtVqzZwDgkk,10531
22
+ jstdata/workflows/tutorial.py,sha256=w2ht5-wpOzUB-YnnbKU68SuHD_PdDBE_pBQn8zL-QK4,5025
23
+ jstdata/workflows/tutorial_host.py,sha256=_b3ycgrZpAhFcUYPQnnmtVZyDfmpP9oUdj4BL3rCHLk,5902
24
+ jstdata-0.2.0.dist-info/METADATA,sha256=fYJl31donCLLoqq8MHsnA94M45ITcte6AbhHyP0JrbE,6679
25
+ jstdata-0.2.0.dist-info/WHEEL,sha256=EGEvSphFYqXKs23-kQBeyNoJP1nrT8ZJKQoi5p5DYL8,88
26
+ jstdata-0.2.0.dist-info/entry_points.txt,sha256=LiXk2o_qORp1dSOntBHUmOm4h1RKB4PFCPVnpw6V69E,39
27
+ jstdata-0.2.0.dist-info/licenses/LICENSE,sha256=6GgMUDMJtgsxaYAeTpyCFH6t6bNVNVUQsgkUKLQxyoo,1068
28
+ jstdata-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.4.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ jst=jstdata.cli:cli
3
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mahfuj Ahbab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT of OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.