session-to-obsidian 0.1.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.
- session_to_obsidian-0.1.0/.github/workflows/ci.yml +18 -0
- session_to_obsidian-0.1.0/.github/workflows/release.yml +19 -0
- session_to_obsidian-0.1.0/.gitignore +9 -0
- session_to_obsidian-0.1.0/CHANGELOG.md +25 -0
- session_to_obsidian-0.1.0/CONTRIBUTING.md +40 -0
- session_to_obsidian-0.1.0/LICENSE +21 -0
- session_to_obsidian-0.1.0/PKG-INFO +93 -0
- session_to_obsidian-0.1.0/README.md +73 -0
- session_to_obsidian-0.1.0/README.zh-TW.md +69 -0
- session_to_obsidian-0.1.0/SPEC.md +58 -0
- session_to_obsidian-0.1.0/pyproject.toml +34 -0
- session_to_obsidian-0.1.0/s2o/__init__.py +2 -0
- session_to_obsidian-0.1.0/s2o/adapters/__init__.py +29 -0
- session_to_obsidian-0.1.0/s2o/adapters/claude.py +15 -0
- session_to_obsidian-0.1.0/s2o/adapters/copilot.py +156 -0
- session_to_obsidian-0.1.0/s2o/cli.py +178 -0
- session_to_obsidian-0.1.0/s2o/cluster.py +118 -0
- session_to_obsidian-0.1.0/s2o/i18n.py +57 -0
- session_to_obsidian-0.1.0/s2o/index.py +64 -0
- session_to_obsidian-0.1.0/s2o/parse.py +147 -0
- session_to_obsidian-0.1.0/s2o/render.py +154 -0
- session_to_obsidian-0.1.0/s2o/slugs.py +33 -0
- session_to_obsidian-0.1.0/skill/SKILL.md +33 -0
- session_to_obsidian-0.1.0/tests/test_s2o.py +195 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
jobs:
|
|
7
|
+
test:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
strategy:
|
|
10
|
+
matrix:
|
|
11
|
+
python-version: ["3.10", "3.12"]
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: ${{ matrix.python-version }}
|
|
17
|
+
- run: pip install pytest -e .
|
|
18
|
+
- run: pytest -q
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
# 推一個版本 tag(例如 v0.1.0)就自動 build + 發佈到 PyPI。
|
|
3
|
+
# 用 PyPI Trusted Publishing(OIDC),不需要任何 token/secret。
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write # Trusted Publishing 需要;PyPI 用它驗證身分,免 token
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.12"
|
|
17
|
+
- run: pip install build
|
|
18
|
+
- run: python -m build
|
|
19
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. Format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/), and this project adheres to
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-06-19
|
|
10
|
+
|
|
11
|
+
Initial release.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- `s2o import <file> --vault <dir>` — import a Claude Code or GitHub Copilot
|
|
15
|
+
Chat session into Obsidian as a topic-organized recap note + topic-grouped
|
|
16
|
+
Canvas, and update a cross-session `INDEX.md`.
|
|
17
|
+
- Auto source detection: Claude native jsonl, Copilot Chat append-log (replayed).
|
|
18
|
+
- Semantic topic clustering via the local `claude` CLI (headless, no API key);
|
|
19
|
+
`--cluster time` fallback with no LLM.
|
|
20
|
+
- Bilingual output (`--lang en|zh-TW`, default `en`; `S2O_LANG` env).
|
|
21
|
+
- Frontmatter `originSessionId` for dedupe — re-importing a session replaces it.
|
|
22
|
+
- `s2o index` to rebuild the index.
|
|
23
|
+
|
|
24
|
+
[Unreleased]: https://github.com/timo9378/session-to-obsidian/compare/v0.1.0...HEAD
|
|
25
|
+
[0.1.0]: https://github.com/timo9378/session-to-obsidian/releases/tag/v0.1.0
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for your interest! This is a small, dependency-free tool — contributions that keep it simple are very welcome.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/timo9378/session-to-obsidian
|
|
9
|
+
cd session-to-obsidian
|
|
10
|
+
pip install pytest -e . # or use a venv
|
|
11
|
+
pytest
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Project layout
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
s2o/
|
|
18
|
+
parse.py jsonl → steps (no LLM)
|
|
19
|
+
adapters/ source detection + restore (copilot append-log replay, claude native)
|
|
20
|
+
cluster.py ask-list → {title, topics}; backends: claude_cli (host Claude), time
|
|
21
|
+
render.py steps + topics → .md (TOC + topic sections) + .canvas
|
|
22
|
+
index.py sessions/ → INDEX.md
|
|
23
|
+
slugs.py title → cross-platform filename slug
|
|
24
|
+
i18n.py output labels + clustering prompt (en / zh-TW)
|
|
25
|
+
cli.py orchestration (import / index)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Design rationale lives in [SPEC.md](SPEC.md) — read it before changing clustering or rendering.
|
|
29
|
+
|
|
30
|
+
## Guidelines
|
|
31
|
+
|
|
32
|
+
- **No runtime dependencies.** Standard library only. The LLM step shells out to the `claude` CLI.
|
|
33
|
+
- Add a test for any bug you fix or behavior you add (`tests/test_s2o.py`).
|
|
34
|
+
- The hard-won bits (Copilot append-log replay, heading-demote + fence-balancing, topic clustering contract) are covered by tests — keep them green.
|
|
35
|
+
- Adding a new source (e.g. another AI IDE)? Write an adapter in `s2o/adapters/` returning `(records, meta)` and wire it into `detect()`.
|
|
36
|
+
- Adding a language? Extend `LABELS` and `_RULES_NAME` in `s2o/i18n.py`.
|
|
37
|
+
|
|
38
|
+
## Reporting issues
|
|
39
|
+
|
|
40
|
+
Include the source type (Claude / Copilot), the command you ran, and what the output looked like vs what you expected.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 timo9378
|
|
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.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: session-to-obsidian
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 把 Claude Code / GitHub Copilot 對話 session 變成 Obsidian 裡依語意主題分區、可翻找、可給 Claude compact 後回想的回顧筆記
|
|
5
|
+
Project-URL: Homepage, https://github.com/timo9378/session-to-obsidian
|
|
6
|
+
Project-URL: Repository, https://github.com/timo9378/session-to-obsidian
|
|
7
|
+
Project-URL: Issues, https://github.com/timo9378/session-to-obsidian/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/timo9378/session-to-obsidian/blob/main/CHANGELOG.md
|
|
9
|
+
Author: timo9378
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: claude-code,copilot,knowledge-management,llm,obsidian
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Utilities
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# session-to-obsidian
|
|
22
|
+
|
|
23
|
+
[](https://github.com/timo9378/session-to-obsidian/actions/workflows/ci.yml)
|
|
24
|
+
[](https://pypi.org/project/session-to-obsidian/)
|
|
25
|
+
[](https://www.python.org/)
|
|
26
|
+
[](LICENSE)
|
|
27
|
+
|
|
28
|
+
Turn a **Claude Code** or **GitHub Copilot Chat** session into an Obsidian note that is organized **by topic**, easy to browse, good for writing from, and useful as a memory layer your AI can recall after a context compaction.
|
|
29
|
+
|
|
30
|
+
繁體中文說明見 **[README.zh-TW.md](README.zh-TW.md)**.
|
|
31
|
+
|
|
32
|
+
Not a verbatim log dump. It splits one session into **coherent topic threads** (not time slices). Under each topic: *what you asked + the assistant's full narrative + the tools/files/screenshots involved*, with a table of contents on top — plus a cross-session `INDEX.md`.
|
|
33
|
+
|
|
34
|
+
## Why
|
|
35
|
+
|
|
36
|
+
One AI coding session often does several unrelated things (fix a bug, tweak some UI, detour into something else). Later you want to find "how did I solve that thing" — and a timeline is useless, because what you remember is the **topic**, not the timestamp. This tool uses an LLM to cluster steps into topics so the note is organized the way you remember it.
|
|
37
|
+
|
|
38
|
+
The first audience is the author's own future self (a tool you keep using); open source is a bonus.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pipx install session-to-obsidian
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Topic clustering calls your **local `claude` CLI** (Claude Code) in headless mode — no API key, uses your existing subscription. No Claude Code? It still works, clustering just degrades to time-slicing (see below).
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# A Claude Code session (jsonl lives in ~/.claude/projects/<proj>/<uuid>.jsonl)
|
|
52
|
+
s2o import ~/.claude/projects/myproj/abc123.jsonl --vault ~/Obsidian
|
|
53
|
+
|
|
54
|
+
# A Copilot Chat session (VS Code's chatSessions/<uuid>.jsonl append-log)
|
|
55
|
+
s2o import "~/…/workspaceStorage/<hash>/chatSessions/<uuid>.jsonl" --vault ~/Obsidian
|
|
56
|
+
|
|
57
|
+
# Output language (labels + generated titles). Default: en
|
|
58
|
+
s2o import <file> --vault ~/Obsidian --lang zh-TW
|
|
59
|
+
|
|
60
|
+
# No LLM — pure time-segmentation (offline / no Claude Code)
|
|
61
|
+
s2o import <file> --vault ~/Obsidian --cluster time
|
|
62
|
+
|
|
63
|
+
# Rebuild the index only
|
|
64
|
+
s2o index --vault ~/Obsidian
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Source (Claude native / Copilot append-log) is auto-detected. Output goes to `<vault>/90-Meta/sessions/<date>-<source>-<title>/` with a `.md` (recap) and `.canvas` (topic-grouped node graph). Set `S2O_VAULT` to skip `--vault`; `S2O_LANG` to set a default language. Re-importing the same session replaces the previous note (deduped by an `originSessionId` stored in frontmatter).
|
|
68
|
+
|
|
69
|
+
## How it works
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
detect source → adapter restores steps → extract "ask + files + first-line gist"
|
|
73
|
+
→ cluster (LLM) → render .md/.canvas → update INDEX.md
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Clustering only feeds the LLM **the ask list + each step's touched filenames + the assistant's first line** (never the full transcript), so token use is bounded. Compound asks go to their dominant topic; topics may be non-contiguous in time; each step keeps its original number.
|
|
77
|
+
|
|
78
|
+
## Limitations
|
|
79
|
+
|
|
80
|
+
- **Topic clustering needs the local `claude` CLI** (Claude Code). Without it, use `--cluster time`.
|
|
81
|
+
- Images are not extracted by default (`--images` to enable). By Obsidian convention images live under `_attachments/` (gitignore them; sync via Syncthing to avoid bloating git).
|
|
82
|
+
- Topic names / titles are LLM-generated; quality depends on the model. Re-run a single session to regenerate.
|
|
83
|
+
|
|
84
|
+
## Development
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install pytest -e .
|
|
88
|
+
pytest
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# session-to-obsidian
|
|
2
|
+
|
|
3
|
+
[](https://github.com/timo9378/session-to-obsidian/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/session-to-obsidian/)
|
|
5
|
+
[](https://www.python.org/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Turn a **Claude Code** or **GitHub Copilot Chat** session into an Obsidian note that is organized **by topic**, easy to browse, good for writing from, and useful as a memory layer your AI can recall after a context compaction.
|
|
9
|
+
|
|
10
|
+
繁體中文說明見 **[README.zh-TW.md](README.zh-TW.md)**.
|
|
11
|
+
|
|
12
|
+
Not a verbatim log dump. It splits one session into **coherent topic threads** (not time slices). Under each topic: *what you asked + the assistant's full narrative + the tools/files/screenshots involved*, with a table of contents on top — plus a cross-session `INDEX.md`.
|
|
13
|
+
|
|
14
|
+
## Why
|
|
15
|
+
|
|
16
|
+
One AI coding session often does several unrelated things (fix a bug, tweak some UI, detour into something else). Later you want to find "how did I solve that thing" — and a timeline is useless, because what you remember is the **topic**, not the timestamp. This tool uses an LLM to cluster steps into topics so the note is organized the way you remember it.
|
|
17
|
+
|
|
18
|
+
The first audience is the author's own future self (a tool you keep using); open source is a bonus.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pipx install session-to-obsidian
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Topic clustering calls your **local `claude` CLI** (Claude Code) in headless mode — no API key, uses your existing subscription. No Claude Code? It still works, clustering just degrades to time-slicing (see below).
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# A Claude Code session (jsonl lives in ~/.claude/projects/<proj>/<uuid>.jsonl)
|
|
32
|
+
s2o import ~/.claude/projects/myproj/abc123.jsonl --vault ~/Obsidian
|
|
33
|
+
|
|
34
|
+
# A Copilot Chat session (VS Code's chatSessions/<uuid>.jsonl append-log)
|
|
35
|
+
s2o import "~/…/workspaceStorage/<hash>/chatSessions/<uuid>.jsonl" --vault ~/Obsidian
|
|
36
|
+
|
|
37
|
+
# Output language (labels + generated titles). Default: en
|
|
38
|
+
s2o import <file> --vault ~/Obsidian --lang zh-TW
|
|
39
|
+
|
|
40
|
+
# No LLM — pure time-segmentation (offline / no Claude Code)
|
|
41
|
+
s2o import <file> --vault ~/Obsidian --cluster time
|
|
42
|
+
|
|
43
|
+
# Rebuild the index only
|
|
44
|
+
s2o index --vault ~/Obsidian
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Source (Claude native / Copilot append-log) is auto-detected. Output goes to `<vault>/90-Meta/sessions/<date>-<source>-<title>/` with a `.md` (recap) and `.canvas` (topic-grouped node graph). Set `S2O_VAULT` to skip `--vault`; `S2O_LANG` to set a default language. Re-importing the same session replaces the previous note (deduped by an `originSessionId` stored in frontmatter).
|
|
48
|
+
|
|
49
|
+
## How it works
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
detect source → adapter restores steps → extract "ask + files + first-line gist"
|
|
53
|
+
→ cluster (LLM) → render .md/.canvas → update INDEX.md
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Clustering only feeds the LLM **the ask list + each step's touched filenames + the assistant's first line** (never the full transcript), so token use is bounded. Compound asks go to their dominant topic; topics may be non-contiguous in time; each step keeps its original number.
|
|
57
|
+
|
|
58
|
+
## Limitations
|
|
59
|
+
|
|
60
|
+
- **Topic clustering needs the local `claude` CLI** (Claude Code). Without it, use `--cluster time`.
|
|
61
|
+
- Images are not extracted by default (`--images` to enable). By Obsidian convention images live under `_attachments/` (gitignore them; sync via Syncthing to avoid bloating git).
|
|
62
|
+
- Topic names / titles are LLM-generated; quality depends on the model. Re-run a single session to regenerate.
|
|
63
|
+
|
|
64
|
+
## Development
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install pytest -e .
|
|
68
|
+
pytest
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# session-to-obsidian
|
|
2
|
+
|
|
3
|
+
[](https://github.com/timo9378/session-to-obsidian/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/session-to-obsidian/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
把一場 **Claude Code** 或 **GitHub Copilot Chat** 的對話 session,變成 Obsidian 裡**依語意主題分區、可快速翻找、可給 Claude compact 後回想、可拿來寫文章**的回顧筆記。
|
|
8
|
+
|
|
9
|
+
> English: **[README.md](README.md)**
|
|
10
|
+
|
|
11
|
+
不是逐字 log dump。它把一場 session 拆成**連貫的主題串**(不是時間段),每條主題底下是「我問 + 助手完整敘述 + 用到的工具/檔案/截圖」,頂部附目錄,並維護一份跨 session 的 `INDEX.md` 索引。
|
|
12
|
+
|
|
13
|
+
## 為什麼
|
|
14
|
+
|
|
15
|
+
AI coding 一場 session 常常一次解好幾件事(修個 bug、順手改 UI、中間插一段別的)。事後想翻「那個問題當時怎麼解的」,時間軸沒用——你記得的是**主題**,不是幾點做的。這工具用 LLM 把步驟分到主題,讓筆記照你記憶的方式組織。第一受眾是作者本人;開源是順手。
|
|
16
|
+
|
|
17
|
+
## 安裝
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pipx install session-to-obsidian
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
語意分群預設呼叫你**本機的 `claude` CLI**(Claude Code)——零 API 金鑰、吃你現有訂閱。沒裝 Claude Code 也能用,分群退化成時間分段。
|
|
24
|
+
|
|
25
|
+
## 用法
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Claude Code session(jsonl 在 ~/.claude/projects/<專案>/<uuid>.jsonl)
|
|
29
|
+
s2o import ~/.claude/projects/myproj/abc123.jsonl --vault ~/Obsidian
|
|
30
|
+
|
|
31
|
+
# Copilot Chat session(VS Code 的 chatSessions/<uuid>.jsonl append-log)
|
|
32
|
+
s2o import "~/…/workspaceStorage/<hash>/chatSessions/<uuid>.jsonl" --vault ~/Obsidian
|
|
33
|
+
|
|
34
|
+
# 輸出語言(標籤 + 產生的標題),預設 en
|
|
35
|
+
s2o import <file> --vault ~/Obsidian --lang zh-TW
|
|
36
|
+
|
|
37
|
+
# 不靠 LLM,純時間分段(離線 / 無 Claude Code)
|
|
38
|
+
s2o import <file> --vault ~/Obsidian --cluster time
|
|
39
|
+
|
|
40
|
+
# 只重建索引
|
|
41
|
+
s2o index --vault ~/Obsidian
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
來源自動偵測。輸出到 `<vault>/90-Meta/sessions/<日期>-<來源>-<標題>/`(`.md` + `.canvas`)。`S2O_VAULT` 可省 `--vault`,`S2O_LANG` 設預設語言。重匯同一場會自動取代舊版(靠 frontmatter 的 `originSessionId` 去重)。
|
|
45
|
+
|
|
46
|
+
## 它怎麼運作
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
偵測來源 → adapter 還原步驟 → 抽「提問+檔名+助手首句」→ 分群(LLM)→ 渲染 .md/.canvas → 更新 INDEX.md
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
分群只餵**提問清單 + 該步檔名 + 助手首句**(不餵全文),token 有界。複合提問歸主導主題、主題可跨時間非連續、每步保留原始步號。
|
|
53
|
+
|
|
54
|
+
## 限制
|
|
55
|
+
|
|
56
|
+
- **語意分群需要本機 `claude` CLI**(Claude Code)。沒有就 `--cluster time`。
|
|
57
|
+
- 圖片預設不抽(`--images` 開啟);圖走 `_attachments/`(自行 gitignore + Syncthing,避免 git 變肥)。
|
|
58
|
+
- 主題命名/標題由 LLM 產,品質視模型而定;不滿意可重跑單場。
|
|
59
|
+
|
|
60
|
+
## 開發
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install pytest -e .
|
|
64
|
+
pytest
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT — 見 [LICENSE](LICENSE)。
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# session-to-obsidian — 專案規格(SPEC)
|
|
2
|
+
|
|
3
|
+
把 Claude Code session(jsonl)轉成 Obsidian 裡**可快速翻找、可給 Claude compact 後回想、可拿來寫文章**的結構化紀錄。
|
|
4
|
+
|
|
5
|
+
## 1. 目的 / 受眾
|
|
6
|
+
- **單場回顧 + 寫文章素材**:翻找「某問題當時怎麼解的」,加速回憶;`.md` 是主力。
|
|
7
|
+
- **Claude 跨 compact 的記憶層**:context 被壓縮後,Claude 能回去查前面做了什麼/怎麼解 → 這份結構化紀錄是持久記憶。
|
|
8
|
+
- **受眾**:AI coding agent 使用者 ∩ Obsidian 使用者(交集窄)。**第一受眾是作者本人**(永久使用者容器);開源是順手,不以採用率為成敗。
|
|
9
|
+
|
|
10
|
+
## 2. 形態 / 發佈
|
|
11
|
+
- **一個自有專案**,發佈成 **Claude Code skill/plugin(SKILL.md + 腳本)**。
|
|
12
|
+
- **語意那步用 host 的 Claude(零外部 LLM 依賴)**;想跑 standalone/cron 再選配外部/地端 LLM(hybrid)。
|
|
13
|
+
- 「skill」只是呼叫格式,骨子是專案。skill 不穩的疑慮 → 把 agent 的工作限到**只有「主題分群」**,其餘全是死板腳本。
|
|
14
|
+
|
|
15
|
+
## 3. 分工(誰做、要不要 LLM)
|
|
16
|
+
| 步驟 | 誰 | LLM |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| 解析 jsonl → 步驟/提問/工具/檔案/截圖/時間 | 腳本 | ❌ |
|
|
19
|
+
| **把步驟分到主題串 + 命名** | host agent(隔離 sub-agent) | ✅ 唯一一處 |
|
|
20
|
+
| 組 `.md`(目錄 + 主題分區 + 提問當小標 + 完整作法 + 截圖) | 腳本 | ❌ |
|
|
21
|
+
| 建圖 (b) 單場主題級 / (c) 跨 session | 腳本(有標籤就純機械) | ❌ |
|
|
22
|
+
| 儲存(git 文字 / Syncthing 圖) | 腳本 | ❌ |
|
|
23
|
+
|
|
24
|
+
## 4. Token 控制(主題分群)
|
|
25
|
+
- 原則 = **不餵全 transcript**,但給「便宜且高鑑別度」的結構訊號。實測(2026-06-19,dcfce9f1):
|
|
26
|
+
**只餵提問清單會壞** —— context-dependent 的提問(「這是連結」「放到 X 後要 run…」)光看字面無法歸線,
|
|
27
|
+
agent 會把它併進前一條主題、還生出空主題。**修法 = 提問 + 該步碰的檔名(basename,上限 6)+ 助手首句 gist(上限 160 字)**,
|
|
28
|
+
量仍有界(每步 ≈ 一兩百字),救回 context-dependent 步驟。`--dump-intents` 已內建這三項。
|
|
29
|
+
- 跑在**隔離 sub-agent(Task)**:不吃主 session context、不害它提早 compact。各場 sub-agent 讀自己的 dump、寫自己的 topics.json。
|
|
30
|
+
- 分群契約:每步恰好一主題(不重不漏,render 端驗證);**主題可非連續**(隔時收尾步歸回原線);複合提問不拆、歸主導線;
|
|
31
|
+
主題按最早步號排;步號保留原始編號(主題視圖仍可還原時序)。tiny 場(≤5步同一事)→ 1 主題,別硬切。
|
|
32
|
+
|
|
33
|
+
## 5. 輸出
|
|
34
|
+
- **`<slug>.md`**:每步「我問 + 助手完整敘述 + 工具/檔案 + 截圖」,依主題分區 + 頂部目錄。**核心價值**。導覽靠 Obsidian 原生 Outline/搜尋。
|
|
35
|
+
- **(b) 單場主題級節點圖**:主題串當節點(非每步、非檔案 hub —— 那是毛球教訓)。對「一場多主題」的 session 有用。
|
|
36
|
+
- **(c) 跨 session 圖**:每場一節點。關聯訊號(由強到弱):**共用檔案(權重,設門檻)+ 同專案分簇 + 共用主題標籤(白拿)**;**embedding 語意相似 = opt-in**(新依賴);模糊字串搜尋最弱、不用。避免毛球 → 只用強邊。
|
|
37
|
+
|
|
38
|
+
## 6. 儲存架構(已定案)
|
|
39
|
+
- **git 只放文字**(.md/.canvas)→ 永不肥。
|
|
40
|
+
- **圖走 Syncthing 的 vault `_attachments/`**(gitignore)→ server↔手機,免 git bloat。
|
|
41
|
+
- 全圖原檔私有封存;文章要用的圖才逐張走 blog pipeline 公開(session 截圖含敏感,別全公開)。
|
|
42
|
+
- 手機:termux SSH(免 PAT)或 Obsidian-Git(HTTPS+PAT);**手機 pull-only**(多寫手會打架);Android 共用儲存 `git config core.filemode false`。
|
|
43
|
+
|
|
44
|
+
## 7. 未解 / 待做(下次開工)
|
|
45
|
+
- ✅ **主題分群 step**(2026-06-19 完成):`--dump-intents`(提問+files+gist)→ 隔離 sub-agent 分群+命名+總標題 → `--topics` render。20 場已重匯。
|
|
46
|
+
- ✅ **.md 導覽**:頂部 TOC(wikilink heading 連結)+ 主題分區 + 原始步號;助手敘述的 ATX 標題 demote 成粗體、fence 強制配對 → Obsidian Outline 只剩 主題/步驟。
|
|
47
|
+
- ✅ **手刻 mesh 砍掉**(候選3降範圍);單場 `.canvas` 改主題分組彩色框。
|
|
48
|
+
- **(b)(c) 圖的呈現形式**:仍未做。傾向原生 graph(linked notes,scoped local graph)而非手刻布局。跨 session(c)未動。
|
|
49
|
+
- ✅ **檔名帶標題**(2026-06-19):session 資料夾/檔名 = `<date>-<source>-<標題slug>`(跨平台 sanitize + 截長),側欄一眼看出主題,不再是 uuid。
|
|
50
|
+
- ✅ **recall 入口**(2026-06-19):`build_index.py` 掃 `sessions/*/*.md` 產 `INDEX.md`(全 session + 主題地圖,按月分組)。
|
|
51
|
+
雙受眾:人一頁掃完、Claude compact 後讀這張當 recall 地圖(找到 → 開 .md → Outline 跳主題)。慣例已寫進 vault `CLAUDE.md`。
|
|
52
|
+
進階(未做):`/recall <query>` skill 直接回傳命中主題(屬候選5打包範圍)。
|
|
53
|
+
- **打包成 skill/plugin**(SKILL.md 把 extract→cluster→render 串起來)+ 選配 standalone CLI。骨架已成形(三段拆分),待寫 SKILL.md。
|
|
54
|
+
|
|
55
|
+
## 現況(2026-06-19)
|
|
56
|
+
`session_to_obsidian.py`:解析 → `--dump-intents`(提問+files+gist 給分群 agent)→ `--topics`(吃 {title,topics} render 主題分區 .md + TOC + 主題分組 canvas);沒給 `--topics` 退回時間分段。`NOIMG=1` 純文字。
|
|
57
|
+
**20 場全部以主題分群重匯完成**(9 copilot + 11 Claude),coverage/headings/fence parity 全驗證通過。dump/topics 暫存 `/tmp/s2o/`。mesh 已移除。
|
|
58
|
+
待:SKILL.md 打包、recall 入口、(b)(c) 圖、選配重抽圖(目前 text-only)。
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "session-to-obsidian"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "把 Claude Code / GitHub Copilot 對話 session 變成 Obsidian 裡依語意主題分區、可翻找、可給 Claude compact 後回想的回顧筆記"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "timo9378" }]
|
|
14
|
+
keywords = ["claude-code", "copilot", "obsidian", "llm", "knowledge-management"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Environment :: Console",
|
|
20
|
+
"Topic :: Utilities",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [] # 純標準函式庫;語意分群靠本機 `claude` CLI(選用)
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
s2o = "s2o.cli:main"
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/timo9378/session-to-obsidian"
|
|
29
|
+
Repository = "https://github.com/timo9378/session-to-obsidian"
|
|
30
|
+
Issues = "https://github.com/timo9378/session-to-obsidian/issues"
|
|
31
|
+
Changelog = "https://github.com/timo9378/session-to-obsidian/blob/main/CHANGELOG.md"
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["s2o"]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""來源偵測 + 載入。回傳 (records[Claude格式], meta{date,title})。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import json
|
|
4
|
+
from . import claude, copilot
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def detect(src_path) -> str:
|
|
8
|
+
"""嗅第一行:Copilot append-log 首行有 kind 鍵;Claude 原生是 message/type 訊息。"""
|
|
9
|
+
with open(src_path, encoding="utf-8") as f:
|
|
10
|
+
for line in f:
|
|
11
|
+
if not line.strip():
|
|
12
|
+
continue
|
|
13
|
+
try:
|
|
14
|
+
o = json.loads(line)
|
|
15
|
+
except Exception:
|
|
16
|
+
return "claude"
|
|
17
|
+
return "copilot" if "kind" in o else "claude"
|
|
18
|
+
return "claude"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load(src_path, source: str = "auto"):
|
|
22
|
+
if source == "auto":
|
|
23
|
+
source = detect(src_path)
|
|
24
|
+
mod = {"claude": claude, "copilot": copilot}.get(source)
|
|
25
|
+
if mod is None:
|
|
26
|
+
raise ValueError(f"未知來源:{source}(支援 claude / copilot / auto)")
|
|
27
|
+
records, meta = mod.load(src_path)
|
|
28
|
+
meta["source"] = source
|
|
29
|
+
return records, meta
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Claude Code 原生 session(jsonl)— 本來就是 Claude 格式,讀進來即可。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from ..parse import read_jsonl, session_id
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def load(src_path):
|
|
7
|
+
"""→ (records, meta)。meta.date = 第一筆 timestamp;session_id 供去重;title 留空。"""
|
|
8
|
+
records = read_jsonl(src_path)
|
|
9
|
+
date = ""
|
|
10
|
+
for r in records:
|
|
11
|
+
ts = r.get("timestamp")
|
|
12
|
+
if ts:
|
|
13
|
+
date = ts
|
|
14
|
+
break
|
|
15
|
+
return records, {"date": date, "title": "", "session_id": session_id(records)}
|