@pi-claudian/auto-save-to-markdown 0.3.2 → 0.3.3
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.
- package/README.md +11 -8
- package/README.zh.md +8 -6
- package/index.ts +30 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -78,14 +78,14 @@ session_file: "~/.pi/agent/sessions/--Users-me-project-20260829-050500_ab12.json
|
|
|
78
78
|
|
|
79
79
|
# Fix login redirect loop
|
|
80
80
|
|
|
81
|
-
User
|
|
81
|
+
User <span style="font-size: 0.5em; color: var(--text-faint);">2026-08-29 13:05:12</span>
|
|
82
82
|
===
|
|
83
83
|
|
|
84
84
|
The login page redirects in a loop after the auth refactor...
|
|
85
85
|
|
|
86
86
|
---
|
|
87
87
|
|
|
88
|
-
Assistant
|
|
88
|
+
Assistant <span style="font-size: 0.5em; color: var(--text-faint);">2026-08-29 13:05:40 · claude-sonnet-4-5</span>
|
|
89
89
|
===
|
|
90
90
|
|
|
91
91
|
> [!tldr]- Thinking
|
|
@@ -113,12 +113,15 @@ previews are wrapped in inline code spans (with a delimiter sized to survive
|
|
|
113
113
|
backticks inside the content), so raw tool output renders literally instead of
|
|
114
114
|
being parsed as markdown.
|
|
115
115
|
|
|
116
|
-
Each message block opens with a setext level-1 info header (`User
|
|
117
|
-
underlined with `===`
|
|
118
|
-
typically starts with, and distinguishable from content `#` headings
|
|
119
|
-
parsing
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
Each message block opens with a setext level-1 info header (`User`,
|
|
117
|
+
`Assistant`) underlined with `===` — one level above the `##` headings AI
|
|
118
|
+
content typically starts with, and distinguishable from content `#` headings
|
|
119
|
+
when parsing. The header's metadata (local date-time, and the model for
|
|
120
|
+
assistant messages) sits in a small faint `<span>` (`0.5em`, Obsidian's
|
|
121
|
+
`--text-faint` color), so the role stays visually dominant while the details
|
|
122
|
+
remain a glance away. Each block ends with a `---` separator wrapped in single
|
|
123
|
+
blank lines (extra blank lines are trimmed), so blocks are easy to tell apart
|
|
124
|
+
both when reading and when splitting the file programmatically.
|
|
122
125
|
|
|
123
126
|
### Fragmented thinking repair
|
|
124
127
|
|
package/README.zh.md
CHANGED
|
@@ -68,14 +68,14 @@ session_file: "~/.pi/agent/sessions/--Users-me-project-20260829-050500_ab12.json
|
|
|
68
68
|
|
|
69
69
|
# 修复登录重定向死循环
|
|
70
70
|
|
|
71
|
-
User
|
|
71
|
+
User <span style="font-size: 0.5em; color: var(--text-faint);">2026-08-29 13:05:12</span>
|
|
72
72
|
===
|
|
73
73
|
|
|
74
74
|
auth 重构之后登录页一直重定向死循环……
|
|
75
75
|
|
|
76
76
|
---
|
|
77
77
|
|
|
78
|
-
Assistant
|
|
78
|
+
Assistant <span style="font-size: 0.5em; color: var(--text-faint);">2026-08-29 13:05:40 · claude-sonnet-4-5</span>
|
|
79
79
|
===
|
|
80
80
|
|
|
81
81
|
> [!tldr]- Thinking
|
|
@@ -101,10 +101,12 @@ Obsidian 对 HTML 块内嵌 Markdown 的渲染不可靠;在非 Obsidian 环境
|
|
|
101
101
|
压过内容中的反引号序列),工具的原始输出因此按字面渲染,不会被当作
|
|
102
102
|
Markdown 解析。
|
|
103
103
|
|
|
104
|
-
每个消息块以 setext 一级信息头(`User
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
每个消息块以 setext 一级信息头(`User`、`Assistant`,下一行以 `===` 下划)
|
|
105
|
+
开头——高于 AI 内容常见的 `##` 二级标题,解析时也能与内容中的 `#` 一级标题
|
|
106
|
+
区分开。信息头的元数据(本地日期时间,assistant 消息还带模型名)放在一个小号
|
|
107
|
+
浅色 `<span>` 中(`0.5em`,Obsidian 的 `--text-faint` 颜色),角色名因此保持
|
|
108
|
+
醒目,细节又触手可及。每个消息块以"上下各一个空行"包裹的 `---` 分隔线结尾
|
|
109
|
+
(多余空行会被裁剪),无论是阅读还是程序化切分,都能清楚地区分每个消息块。
|
|
108
110
|
|
|
109
111
|
### 碎片化 thinking 修复
|
|
110
112
|
|
package/index.ts
CHANGED
|
@@ -21,9 +21,12 @@
|
|
|
21
21
|
* cumulative cost and tokens (input, output, cache read/write), message
|
|
22
22
|
* count, created/updated timestamps, project root and session file.
|
|
23
23
|
* - Body format: every message block opens with a setext-H1 info header
|
|
24
|
-
* (`User
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* (`User <span …>YYYY-MM-DD HH:MM:SS</span>` /
|
|
25
|
+
* `Assistant <span …>YYYY-MM-DD HH:MM:SS · model</span>`, where the span
|
|
26
|
+
* renders the metadata as small faint text — Obsidian CSS variables, so it
|
|
27
|
+
* degrades gracefully elsewhere — underlined with `===`, distinct from the
|
|
28
|
+
* `#`/`##` ATX headings AI content uses) and ends with a `---` separator
|
|
29
|
+
* wrapped in single blank lines.
|
|
27
30
|
* - Tool call/result folding: calls live in the assistant entry while their
|
|
28
31
|
* results are separate toolResult entries; saves pair them by toolCall id
|
|
29
32
|
* and fold each assistant block's calls, with a short result preview each,
|
|
@@ -224,10 +227,28 @@ export default function (pi: ExtensionAPI) {
|
|
|
224
227
|
);
|
|
225
228
|
}
|
|
226
229
|
|
|
227
|
-
/** Local-time
|
|
228
|
-
function
|
|
230
|
+
/** Local-time label for a message entry: YYYY-MM-DD HH:MM:SS. */
|
|
231
|
+
function dateTime(iso: string): string {
|
|
229
232
|
const d = new Date(iso);
|
|
230
|
-
return
|
|
233
|
+
return (
|
|
234
|
+
`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` +
|
|
235
|
+
` ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Wrap header metadata (timestamp, model) in a small faint span: 0.5em text
|
|
241
|
+
* in Obsidian's `--text-faint` color, so the role stays visually dominant.
|
|
242
|
+
* HTML-escaped because the model string lands inside a raw HTML span.
|
|
243
|
+
*/
|
|
244
|
+
function metaSpan(text: string): string {
|
|
245
|
+
const safe = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
246
|
+
return `<span style="font-size: 0.5em; color: var(--text-faint);">${safe}</span>`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Setext-H1 info header: `Role <span …>meta</span>` underlined with `===`. */
|
|
250
|
+
function messageHeader(role: string, meta: string): string {
|
|
251
|
+
return `${role} ${metaSpan(meta)}\n===`;
|
|
231
252
|
}
|
|
232
253
|
|
|
233
254
|
/** Make a string safe for use as a filename component. */
|
|
@@ -387,7 +408,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
387
408
|
// precedes the text it produced, instead of being grouped after the fact.
|
|
388
409
|
// Setext H1 (`===` underline): one level above the `##` headings AI
|
|
389
410
|
// content typically starts with, and distinct from content `#` headings.
|
|
390
|
-
const header =
|
|
411
|
+
const header = messageHeader("Assistant", [t, m.model].filter(Boolean).join(" · "));
|
|
391
412
|
const parts: string[] = [];
|
|
392
413
|
const thinkings: string[] = [];
|
|
393
414
|
const flushThinking = () => {
|
|
@@ -436,9 +457,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
436
457
|
const blocks: string[] = [];
|
|
437
458
|
for (const e of entries) {
|
|
438
459
|
const m = e.message;
|
|
439
|
-
const t =
|
|
460
|
+
const t = dateTime(e.timestamp);
|
|
440
461
|
if (m.role === "user") {
|
|
441
|
-
blocks.push(
|
|
462
|
+
blocks.push(`${messageHeader("User", t)}\n\n${userText(m.content)}`);
|
|
442
463
|
} else if (m.role === "assistant") {
|
|
443
464
|
blocks.push(renderAssistant(m, t, results));
|
|
444
465
|
} else if (m.role === "toolResult") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-claudian/auto-save-to-markdown",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Pi extension that automatically saves each completed conversation turn as a markdown file with YAML frontmatter, one file per session-tree branch.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|