fmind 2.0.2__tar.gz → 2.0.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmind
3
- Version: 2.0.2
3
+ Version: 2.0.3
4
4
  Summary: Read Médéric Hurier's (Fmind) portfolio from the terminal.
5
5
  Keywords: portfolio,cli,fmind,resume,articles
6
6
  Author: Médéric Hurier
@@ -30,14 +30,6 @@ Read [Médéric Hurier's (Fmind)](https://www.fmind.dev/) portfolio from the ter
30
30
  uvx fmind whoami
31
31
  ```
32
32
 
33
- ## Why it stays small
34
-
35
- Every command renders a document the website already publishes — the portfolio at [`/api/profile`](https://www.fmind.dev/api/profile) and, for `fmind read`, the Markdown source of an article at `/articles/<slug>.md`. Those are the same sources behind the site, the Atom feed, `llms.txt` and the MCP server.
36
-
37
- This package carries no copy of the portfolio and needs **no release for content updates**: new articles, a renewed certification or a new engagement appear on the next run. Removing or changing fields that the CLI renders can require a client update; the live compatibility workflow detects those breaks.
38
-
39
- Every command fetches from the website and asks HTTP caches to revalidate. There is no local cache and no `--refresh` option. Each portfolio download has a 15-second total deadline, including connection setup and response reading, and an 8 MiB response limit. An internet connection is required: if the website cannot be read, the command reports an error instead of showing stale information.
40
-
41
33
  ## Commands
42
34
 
43
35
  | Command | Shows |
@@ -158,22 +150,6 @@ The pre-commit hook runs checks without changing or staging files; run `mise run
158
150
 
159
151
  A release tag must match the version in `pyproject.toml` (for example, `v0.1.0`). CD runs the same full gate for that tag and checks the live commands before publishing through PyPI Trusted Publishing. The live smoke check is a release check, not a pull-request gate.
160
152
 
161
- ### Website compatibility
162
-
163
- The **Website compatibility** GitHub Actions workflow runs `mise run smoke` daily at 06:23 UTC, on pushes to `main`, and on manual dispatch. Failed runs use normal GitHub Actions notifications; no issue bot or extra service is needed. The offline CI gate stays independent of website availability.
164
-
165
- The same check exercises every portfolio command in text and both JSON option positions, reads the newest article as Markdown, and launches the real MCP stdio bridge. It discovers the remote tools, resources, templates, and prompts; calls every advertised read-only tool, including article reading with a live slug and the hosting calculator with its defaults; reads resources; and retrieves prompts. The SDK validates tool results against the website's current schemas, and the profile resource is checked against the CLI's rendering contract. API and MCP failures are reported independently. Checks use live data, not snapshots of portfolio facts.
166
-
167
- After a website deployment, run `mise run smoke` locally or dispatch the workflow:
168
-
169
- ```bash
170
- gh workflow run compatibility.yml --repo fmind/cli
171
- ```
172
-
173
- A failure indicates either an outage or a contract change; inspect the failing operation in the run log. Keep `PROFILE_SHAPE` limited to consumed fields and update synthetic fixtures alongside rendering changes. New MCP tools with required inputs may need a sample argument in the smoke check. No release is needed merely to add content or an unused API field.
174
-
175
- Commands exit with 0 on success, 1 for website or article-resolution failures, and 2 for invalid CLI usage.
176
-
177
153
  ## License
178
154
 
179
155
  [MIT](LICENSE) — © 2026 Médéric Hurier (Fmind).
@@ -6,14 +6,6 @@ Read [Médéric Hurier's (Fmind)](https://www.fmind.dev/) portfolio from the ter
6
6
  uvx fmind whoami
7
7
  ```
8
8
 
9
- ## Why it stays small
10
-
11
- Every command renders a document the website already publishes — the portfolio at [`/api/profile`](https://www.fmind.dev/api/profile) and, for `fmind read`, the Markdown source of an article at `/articles/<slug>.md`. Those are the same sources behind the site, the Atom feed, `llms.txt` and the MCP server.
12
-
13
- This package carries no copy of the portfolio and needs **no release for content updates**: new articles, a renewed certification or a new engagement appear on the next run. Removing or changing fields that the CLI renders can require a client update; the live compatibility workflow detects those breaks.
14
-
15
- Every command fetches from the website and asks HTTP caches to revalidate. There is no local cache and no `--refresh` option. Each portfolio download has a 15-second total deadline, including connection setup and response reading, and an 8 MiB response limit. An internet connection is required: if the website cannot be read, the command reports an error instead of showing stale information.
16
-
17
9
  ## Commands
18
10
 
19
11
  | Command | Shows |
@@ -134,22 +126,6 @@ The pre-commit hook runs checks without changing or staging files; run `mise run
134
126
 
135
127
  A release tag must match the version in `pyproject.toml` (for example, `v0.1.0`). CD runs the same full gate for that tag and checks the live commands before publishing through PyPI Trusted Publishing. The live smoke check is a release check, not a pull-request gate.
136
128
 
137
- ### Website compatibility
138
-
139
- The **Website compatibility** GitHub Actions workflow runs `mise run smoke` daily at 06:23 UTC, on pushes to `main`, and on manual dispatch. Failed runs use normal GitHub Actions notifications; no issue bot or extra service is needed. The offline CI gate stays independent of website availability.
140
-
141
- The same check exercises every portfolio command in text and both JSON option positions, reads the newest article as Markdown, and launches the real MCP stdio bridge. It discovers the remote tools, resources, templates, and prompts; calls every advertised read-only tool, including article reading with a live slug and the hosting calculator with its defaults; reads resources; and retrieves prompts. The SDK validates tool results against the website's current schemas, and the profile resource is checked against the CLI's rendering contract. API and MCP failures are reported independently. Checks use live data, not snapshots of portfolio facts.
142
-
143
- After a website deployment, run `mise run smoke` locally or dispatch the workflow:
144
-
145
- ```bash
146
- gh workflow run compatibility.yml --repo fmind/cli
147
- ```
148
-
149
- A failure indicates either an outage or a contract change; inspect the failing operation in the run log. Keep `PROFILE_SHAPE` limited to consumed fields and update synthetic fixtures alongside rendering changes. New MCP tools with required inputs may need a sample argument in the smoke check. No release is needed merely to add content or an unused API field.
150
-
151
- Commands exit with 0 on success, 1 for website or article-resolution failures, and 2 for invalid CLI usage.
152
-
153
129
  ## License
154
130
 
155
131
  [MIT](LICENSE) — © 2026 Médéric Hurier (Fmind).
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fmind"
3
- version = "2.0.2"
3
+ version = "2.0.3"
4
4
  description = "Read Médéric Hurier's (Fmind) portfolio from the terminal."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,7 +1,7 @@
1
1
  # https://packaging.python.org/en/latest/specifications/pyproject-toml/
2
2
  [project]
3
3
  name = "fmind"
4
- version = "2.0.2"
4
+ version = "2.0.3"
5
5
  description = "Read Médéric Hurier's (Fmind) portfolio from the terminal."
6
6
  readme = "README.md"
7
7
  license = "MIT"
@@ -158,10 +158,15 @@ def _validate(value: object, shape: object, path: str) -> None:
158
158
  raise FmindError(f"the profile endpoint returned an invalid portfolio document at {path}")
159
159
 
160
160
 
161
+ def _reject_constant(name: str) -> None:
162
+ """Refuse NaN and Infinity: they are not JSON, so `--json` could not forward them."""
163
+ raise ValueError(name)
164
+
165
+
161
166
  def _parse_profile(payload: bytes) -> dict[str, Any]:
162
167
  """Decode the portfolio document, rejecting anything that is not one."""
163
168
  try:
164
- document = json.loads(payload)
169
+ document = json.loads(payload, parse_constant=_reject_constant)
165
170
  except (ValueError, RecursionError) as error:
166
171
  raise FmindError("the profile endpoint did not return JSON") from error
167
172
  # Python's JSON decoder accepts lone surrogates. Check keys and future fields
@@ -116,7 +116,10 @@ def _write(text: str) -> None:
116
116
  def _emit(ctx: typer.Context, body: RenderableType, payload: Any, *, as_json: bool, page: bool = False) -> None:
117
117
  """Print either the rendered section or its JSON data."""
118
118
  if as_json or ctx.find_root().params["as_json"]:
119
- _write(json.dumps(payload, ensure_ascii=False, indent=2) + "\n")
119
+ # json.dumps escapes C0 controls but not DEL or C1 (such as CSI); fields the
120
+ # renderer does not validate still reach the terminal, so escape those too.
121
+ text = json.dumps(payload, ensure_ascii=False, indent=2)
122
+ _write(CONTROL_PATTERN.sub(lambda match: f"\\u{ord(match.group()):04x}", text) + "\n")
120
123
  return
121
124
  out = _console(ctx)
122
125
  if not out.is_terminal:
File without changes
File without changes
File without changes
File without changes
File without changes