Skip to content

Commit a829bf6

Browse files
yjwyjw
authored andcommitted
feat: streamline chat and report workflows for 0.2.5
1 parent 3bb4fbf commit a829bf6

24 files changed

Lines changed: 1018 additions & 142 deletions

CHANGELOG.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.2.5] - 2026-06-19
9+
10+
### Added
11+
- Added an authoritative chat slash-command whitelist and persistent `balanced`, `buffett`, `munger`, `graham`, and `dalio` analysis styles.
12+
- Added A-share session detection for closed, pre-market, morning, midday, intraday, and after-hours report routing.
13+
- Added safe clickable HTTP(S) news links to Markdown and PDF reports.
14+
15+
### Changed
16+
- `young daily --llm` now generates or reuses matching Markdown and PDF artifacts; `--refresh` forces a rebuild.
17+
- `young replay`, `/replay`, and `/daily-llm` remain compatibility aliases but now direct users to `young daily --llm`.
18+
- `young report` remains a Markdown-to-PDF exporter and defaults to the latest available trading-date report.
19+
- Public reports now use one concise disclosure under the title and natural missing-data language instead of mechanical placeholder paragraphs.
20+
- Updated README guidance to match the current command surface: `young daily` is the deterministic watchlist report, `young daily --llm` is the deep after-hours replay with shared identity and `--refresh` rebuilds, `young replay`/`/daily-llm`/`/replay` are deprecated aliases, and chat documents its curated authoritative whitelist plus the `/style` framework.
21+
- Updated install and export guidance to emphasize `young init` as an initialization/verification step, `young report` as the Markdown-to-PDF export path, and the PDF/report hygiene rules that keep links clickable and remove mechanical placeholder wording.
22+
23+
### Fixed
24+
- Prevented chat from recommending nonexistent commands or invoking blocked configuration, delivery, update, uninstall, and other mutating command paths.
25+
- Made PDF export select an explicit report identity instead of relying on modification-time ordering when multiple same-day reports exist.
26+
- Kept missing LLM configuration recoverable through deterministic daily output while preserving authentication and network failures as errors.
27+
828
## [0.2.4] - 2026-06-19
929

1030
### Added
@@ -58,7 +78,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5878
### Added
5979
- Added versioned LLM and delivery-channel configuration in `~/.young_stock/config.json`, including masked display and environment-based API key lookup.
6080
- Added OpenAI-compatible providers (OpenAI, DeepSeek, Qwen, Ollama) plus Anthropic message support with timeouts, retries, and safe errors.
61-
- Added `young chat`, a Rich interactive REPL whose slash commands reuse the existing Click command tree.
81+
- Added `young chat`, a Rich interactive REPL whose curated authoritative whitelist routes selected safe/read-only commands through Click.
6282
- Added evidence-driven `young replay`, `young daily --llm`, and `young analyze <symbol>` workflows based on the stock-analysis M1-M6 methodology and quality-score degradation.
6383
- Added persistent report/evidence artifacts under `~/.young_stock/reports/YYYYMMDD/`.
6484
- Added `young report` with automatic deterministic-report fallback and optional WeasyPrint PDF export using a Kami-compatible Equity Report layout.

README.md

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,19 @@ Born out of a real workflow: every trading day after close I wanted the same fiv
2020
Recommended for CLI isolation:
2121

2222
```bash
23-
uv tool install young-stock-cli
24-
young init
23+
uv tool install young-stock-cli && young init
2524
```
2625

2726
Or install into the active Python environment:
2827

2928
```bash
30-
python3 -m pip install young-stock-cli
29+
python3 -m pip install --upgrade young-stock-cli && young init
3130
```
3231

32+
If you need the plain install form for an existing environment, `python3 -m pip install young-stock-cli` still works.
33+
If you upgraded from an older uv-managed environment, `uv tool install --force 'young-stock-cli'` is the quick
34+
refresh path.
35+
3336
Requires Python 3.9+.
3437

3538
If `pip3 install young-stock-cli` reports that every release requires a different Python version, your
@@ -47,8 +50,9 @@ uv tool install --upgrade young-stock-cli
4750
```
4851

4952
`young init` creates the local home/profile files, verifies whether PDF rendering is available in the current
50-
environment, and prints the recommended next steps. You only need to install the tool once per environment; you do
51-
not need to reinstall it before every report.
53+
environment, and prints the recommended next steps. It only initializes and verifies; it does not auto-edit your
54+
shell environment or install extra packages. You only need to install the tool once per environment; you do not need
55+
to reinstall it before every report.
5256

5357
If `python3 -m pip install --upgrade young-stock-cli` succeeds but `young --version` still shows an older release,
5458
you are probably running a different executable entrypoint than the interpreter you just upgraded. A quick check:
@@ -80,15 +84,16 @@ young profile add-fund 021528 --buy-date 2026-01-10 --quantity 1000
8084
young profile list
8185
young profile clear-stocks # clear all saved stocks/ETFs only
8286
young profile clear-funds # clear all saved funds only
83-
young daily --format summary # concise personalized daily report
84-
young daily --format key-points # short report with trend/risk points
85-
young daily --format full # full personalized daily report
86-
young daily --llm # evidence-driven deep replay with your configured LLM
87-
young init # initialize local state and verify report/LLM readiness
88-
young replay # deep M1-M6 market replay
87+
young daily --format summary # deterministic watchlist daily report
88+
young daily --format key-points # short deterministic report with trend/risk points
89+
young daily --format full # full deterministic watchlist daily report
90+
young daily --llm # deep after-hours / latest-trading-day replay; closes MD+PDF
91+
young daily --llm --refresh # rebuild the same identity from scratch
92+
young init # initialize local state and verify report/PDF readiness
93+
young replay # deprecated alias for young daily --llm
8994
young analyze 600519 # deep single-stock analysis
9095
young chat # Rich chat mode with slash commands
91-
young report # export latest Markdown as a Kami-style PDF
96+
young report # export the latest Markdown report to PDF
9297
young send # send latest Markdown + PDF to configured channels
9398
young daily --only 基金,A股 --quick
9499
young news 3690.HK # multi-source news only
@@ -190,8 +195,8 @@ young config llm --provider ark --model "<ark-model-or-endpoint-id>" --api-key-e
190195
young config llm --provider kimi --model "<moonshot-model-id>" --api-key-env MOONSHOT_API_KEY
191196
```
192197

193-
Start the interactive mode with `young chat`. Slash commands reuse the same Click command tree, so traditional CLI
194-
and chat behavior stay aligned:
198+
Start the interactive mode with `young chat`. A curated authoritative whitelist routes selected safe/read-only
199+
commands through Click:
195200

196201
```text
197202
/a
@@ -201,19 +206,29 @@ and chat behavior stay aligned:
201206
/daily --llm
202207
/daily-llm
203208
/replay
204-
/analyze 600519
205209
/profile list
210+
/style
211+
/style list
212+
/style set balanced
213+
/style show
214+
/style clear
206215
/report
207-
/send
208216
```
209217

210218
The chat keeps the five most recent user/assistant turns. If no LLM is configured, enhanced commands fail with a
211-
configuration hint while `/a`, `/stock`, `/daily`, and every other deterministic command continue working.
219+
configuration hint while `/a`, `/stock`, `/daily`, and every other deterministic command continue working. Chat is
220+
authoritative and only exposes a white-listed command surface: do not expect `/market` or `/trend`, and `/send`,
221+
`/config`, `/update`, and `/uninstall` are intentionally blocked from chat. `/daily-llm` and `/replay` are kept as
222+
deprecated aliases that route to `/daily --llm`.
223+
224+
The style commands persist under chat config and only change the analysis frame, not persona identity. Supported
225+
styles are `balanced`, `buffett`, `munger`, `graham`, and `dalio`.
212226

213227
### Evidence-driven deep replay
214228

215-
`young replay`, `young daily --llm`, and `/replay` build an Evidence Pack entirely from the structured values returned
216-
by `young_stock._core`. The model is used for synthesis, not data collection.
229+
`young daily --llm` builds an Evidence Pack entirely from the structured values returned by `young_stock._core`.
230+
`young replay` and `/replay` are deprecated aliases for the same workflow. The model is used for synthesis, not data
231+
collection.
217232

218233
The report follows the six-module method from
219234
[`AdvancingTitans/stock-analysis`](https://github.com/AdvancingTitans/stock-analysis):
@@ -228,9 +243,9 @@ The report follows the six-module method from
228243
Before each LLM replay, young checks the remote `stock-analysis` version. It installs a text-only update only when
229244
the remote semantic version is greater than the locally cached/bundled version. `SKILL.md`, output discipline,
230245
data-source strategy, M1-M6 methodology files, and report templates are downloaded together, SHA-256 recorded in
231-
`~/.young_stock/methodologies/stock-analysis/manifest.json`, and verified when read. Remote code is never executed.
232-
If checking, downloading, or validation fails, young keeps the last verified local specification or bundled 4.2.0
233-
guidance.
246+
`~/.young_stock/methodologies/stock-analysis/manifest.json`, and verified when read. Remote code is never executed,
247+
and the browser or external repo is not a chat runtime dependency. If checking, downloading, or validation fails,
248+
young keeps the last verified local specification or bundled guidance.
234249

235250
Each module has an evidence score. Missing fields remain missing rather than being rendered as zero. Low-quality
236251
evidence automatically produces a shorter report limited to verified indices, holdings, risks, and next-session
@@ -246,9 +261,12 @@ report-writing context. The returned Markdown is reviewed again before it can be
246261
use only these source phrases:
247262

248263
- normal data: `据公开市场数据`, `据交易所及财经终端披露`
249-
- missing data: `该指标当日未披露`, `历史数据不可得`, `本模块证据暂缺`
264+
- missing data: `该指标当日未披露`, `历史数据不可得`
250265
- historical lookback: `按惯例回溯至该日`, `历史口径回溯`
251266

267+
The mechanical placeholder `本模块证据暂缺` is cleaned from the final publication text and should not be treated as a
268+
recommended output phrase.
269+
252270
Report artifacts include the date, market session, and topic:
253271

254272
```text
@@ -266,7 +284,7 @@ is retained.
266284
The standard install already includes PDF support. If you upgraded from an older environment, refresh the tool once:
267285

268286
```bash
269-
uv tool install --force 'young-stock-cli'
287+
uv tool install --upgrade young-stock-cli
270288
```
271289

272290
If `young` was installed into the active Python environment instead:
@@ -275,16 +293,20 @@ If `young` was installed into the active Python environment instead:
275293
python3 -m pip install --upgrade young-stock-cli
276294
```
277295

278-
Then export the latest report:
296+
Then export the latest Markdown report to PDF:
279297

280298
```bash
281299
young report
282300
young report --date 20260618
283301
```
284302

285303
If no Markdown report exists for the selected date, `young report` first reuses a saved diary entry when available,
286-
otherwise it automatically generates a deterministic full daily report. Run `young init` first if you want a quick
287-
readiness check for PDF rendering, config, and local storage paths.
304+
otherwise it automatically generates the latest available deterministic daily report. It is a Markdown-to-PDF export
305+
command, not a stdout time-slice report. Run `young init` first if you want a quick readiness check for PDF
306+
rendering, config, and local storage paths.
307+
308+
The PDF header is intentionally short, news links stay clickable, and the final report keeps the publication text
309+
clean rather than echoing mechanical placeholders.
288310

289311
The bundled Equity Report layout follows the
290312
[`tw93/Kami`](https://github.com/tw93/Kami) editorial language: parchment `#f5f4ed`, ink blue `#1B365D`,
@@ -315,7 +337,7 @@ young config channel remove feishu work
315337
One-click workflow:
316338

317339
```bash
318-
young replay
340+
young daily --llm
319341
young report
320342
young send
321343

@@ -355,8 +377,10 @@ The internals are being split into focused modules: `young_stock.calendar` handl
355377
- **Multiple public quote sources** — Sina Finance and Tencent Finance are the default stable quote path; market-specific public interfaces stay behind the CLI fallback layer when no better source is available.
356378
- **Single-stock lookup**`young stock 600519`, `young stock 0700.HK`, or `young stock AAPL` prints a compact quote snapshot with source, trade date, price, change, volume, turnover, market cap, PE/PB, 52-week range when available, and optional news.
357379
- **Fund holding lookup**`young fund 161725` prints the fund's same-day estimated change, latest NAV date, top holdings, holding-stock quotes, rough contribution estimate, and same-day holding-stock news. Official fund NAVs usually update at night, so intraday/close values are clearly labeled as estimates.
358-
- **Personal daily report**`young daily` reads your local investment memory from `~/.young_stock/profile.json`, then prints saved stock/ETF trends, fund estimates, only the markets relevant to your stocks and fund top holdings, and portfolio-style suggestions grounded in your funds, stocks, holding dates, quantities, and available news. First use requires a verified symbol plus `--buy-date` and `--quantity`, for example `young profile add-stock 600519 --buy-date 2026-01-15 --quantity 100` or `young profile add-fund 161725 --buy-date 2026-01-10 --quantity 1000`.
380+
- **Personal daily report**`young daily` is the deterministic watchlist report from your local investment memory in `~/.young_stock/profile.json`; it prints saved stock/ETF trends, fund estimates, only the markets relevant to your symbols and fund top holdings, and portfolio-style suggestions grounded in your funds, stocks, holding dates, quantities, and available news. First use requires a verified symbol plus `--buy-date` and `--quantity`, for example `young profile add-stock 600519 --buy-date 2026-01-15 --quantity 100` or `young profile add-fund 161725 --buy-date 2026-01-10 --quantity 1000`.
381+
- **LLM replay**`young daily --llm` performs the deep after-hours / latest-trading-day replay, then writes the matching Markdown and PDF under the same report identity; `--refresh` forces a rebuild of that identity. `young replay`, `/replay`, and `/daily-llm` are compatibility aliases only.
359382
- **Short report modes**`young daily --format summary` keeps terminal output compact; `--format key-points` adds a few trend/risk bullets; `--only`, `--order`, and `--quick` trim slower or irrelevant sections.
383+
- **Chat style control**`/style list|set|show|clear` persists a framework-only style choice. `balanced`, `buffett`, `munger`, `graham`, and `dalio` are analysis frames, not personas.
360384
- **Investment memory management** — list, remove, clear, and group saved stocks/funds with `young profile list`, `remove-stock`, `remove-fund`, `clear`, `clear-stocks`, `clear-funds`, and `profile group create/add`.
361385
- **Local workflow helpers** — lightweight `portfolio`, `alert`, `note`, and `diary` commands store local records for portfolio experiments, reminder rules, investment notes, and saved daily-report text.
362386
- **Diagnostics**`young diagnose` summarizes recent source health and suggests cache/quick-mode fallbacks when public APIs are unstable; `young diagnose --json` prints read-only, machine-readable support info with Python/version/path/source-health details.
@@ -375,7 +399,7 @@ The internals are being split into focused modules: `young_stock.calendar` handl
375399
- **Same-day news discipline** — news sections only show items published on the requested trading date, up to five items, with a clear empty-state message when nothing valid is available.
376400
- **Readable news links** — linked news items are checked for obvious empty/404/no-content pages and replaced by other same-day news when possible.
377401
- **Sector boards via browser fallback** — when Eastmoney's board API rate-limits, falls back to rendering the public web page (optional, requires a local browser engine).
378-
- **Local updater/uninstaller**`young update` runs `python -m pip install --upgrade young-stock-cli`; `young uninstall` runs `python -m pip uninstall -y young-stock-cli` with the same interpreter that launched the CLI.
402+
- **Local updater/uninstaller**`young update` runs `python -m pip install --upgrade young-stock-cli`; `young uninstall` runs `python -m pip uninstall -y young-stock-cli` with the same interpreter that launched the CLI. For fresh setups, prefer `uv tool install young-stock-cli && young init`, or `python3 -m pip install --upgrade young-stock-cli && young init` in an active interpreter.
379403

380404
## Library usage
381405

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "young-stock-cli"
7-
version = "0.2.4"
7+
version = "0.2.5"
88
description = "A-share (China stock market) after-hours CLI — no login, no scraping tricks, just data."
99
readme = "README.md"
1010
requires-python = ">=3.9"

src/young_stock/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""young-stock-cli: A-share after-hours CLI."""
22

3-
__version__ = "0.2.4"
3+
__version__ = "0.2.5"

src/young_stock/artifacts.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,21 @@
99
from pathlib import Path
1010
from typing import Any
1111

12+
from .calendar import a_share_session
1213
from .local_store import young_home
1314

1415

1516
def market_session(now: datetime | None = None) -> str:
16-
now = now or datetime.now()
17-
minute = now.hour * 60 + now.minute
18-
if 9 * 60 <= minute < 11 * 60 + 30:
19-
return "早盘"
20-
if 11 * 60 + 30 <= minute < 13 * 60:
21-
return "午间"
22-
if 13 * 60 <= minute < 15 * 60:
23-
return "盘中"
24-
return "盘后"
17+
session = a_share_session(now)
18+
return "盘后" if session == "休市" else session
2519

2620

2721
def report_session(trade_date: str, now: datetime | None = None) -> str:
2822
now = now or datetime.now()
2923
if trade_date != now.strftime("%Y%m%d"):
3024
return "盘后"
31-
return market_session(now)
25+
session = market_session(now)
26+
return "盘后" if session == "盘前" else session
3227

3328

3429
@dataclass(frozen=True)

src/young_stock/calendar.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,26 @@ def nearest_trade_date(dt: datetime | None = None, market: str = "a") -> str:
7878
while not is_trade_day(dt, market):
7979
dt -= timedelta(days=1)
8080
return dt.strftime("%Y%m%d")
81+
82+
83+
def a_share_session(dt: datetime | None = None) -> str:
84+
dt = dt or datetime.now()
85+
if not is_trade_day(dt, "a"):
86+
return "休市"
87+
minute = dt.hour * 60 + dt.minute
88+
if minute < 9 * 60:
89+
return "盘前"
90+
if minute < 11 * 60 + 30:
91+
return "早盘"
92+
if minute < 13 * 60:
93+
return "午间"
94+
if minute < 15 * 60:
95+
return "盘中"
96+
return "盘后"
97+
98+
99+
def latest_report_trade_date(dt: datetime | None = None) -> str:
100+
dt = dt or datetime.now()
101+
if a_share_session(dt) in {"休市", "盘前"}:
102+
return nearest_trade_date(dt, "a")
103+
return dt.strftime("%Y%m%d")

0 commit comments

Comments
 (0)